﻿using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ParsecParser;
using ParsecParser.Data;
using ParsecParser.Helpers;
using ParsecParser.Communication;
using ParsecParser.Display;
using System.IO;
using Microsoft.Win32;
using ParsecClient.Classes;
using System.Diagnostics;
using ParsecClient.ParsecRaidWebService;
using System.Configuration;

namespace ParsecClient
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class ParsecWindow : Window
    {
        #region Constants
        private const int MaxConsecutiveLogReadErrors = 8;
        private const int MaxConsecutiveRaidSyncErrors = 3;
        private const int MaxConsecutiveRaidSyncTimeoutErrors = 10;
        private const int MaxConsecutiveMsWithNoData = 25000;
        private const int MaxOutOfCombatRaidSyncs = 2;
        private const int MaxRaidSyncWithNoUpdate = 2;
        private const int RaidUpdateHeartBeatSeconds = 30;
        private const int RaidTimeoutSeconds = 60 * 45;
        private const int FilePollingRate = 100;
        private const int NetworkPollingRate = 3000;
        private const int MovePopOutsToFrontMilliseconds = 5000;
        public enum RaidUpdateModes
        {
            Sleep = 0,
            Passive = 2,
            Active = 3,
            SleepSync = 4
        }
        #endregion

        #region Properties
        private int ConsecutiveLogReadErrors = 0;
        private int ConsecutiveRaidSyncErrors = 0;
        private int ConsecutiveRaidSyncTimeoutErrors = 0;
        private int ConsecutiveOutOfCombatRaidSyncs = 0;
        private int ConsecutiveRaidHeartBeatWithNoUpdate = 0;
        private int ConsecutiveReadsWithNoData = 0;
        public DateTime NextTimeSyncAttempt = new DateTime();
        private string LastOpenedLogFolder;
        public string LogFileFullPath;
        public string LogFolderPath;
        public double AbsorbShieldValue = 0.0;
        private bool IsWatching = false;
        private bool IsRaidConnected = false;
        private bool IsLastFightSelected = true;
        public bool CurrentLogFileIsArchive = false;
        private DateTime? RaidLastUpdatedDate;
        private DateTime LastCombatRaidSync;
        private DateTime LastMovePopOutsToFront;
        private DateTime LastRaidSyncDate;
        private RealTimeLogReader LogReader;
        private Parser LogParser;
        public CombatLog Log;
        private RaidUpdateModes RaidUpdateMode = RaidUpdateModes.Sleep;
        public RaidGroup CurrentRaidGroup = new RaidGroup();
        private CombatLogFilterParameters LogFilterParameters = new CombatLogFilterParameters() { ShowAll = true };

        private object ActiveTimerLock = new object();
        private List<ActiveTimer> ActiveTimers = new List<ActiveTimer>();

        private object ConnectedRaiderLock = new object();
        private List<string> ConnectedRaiders = new List<string>();
        private ConcurrentQueue<int> LastTenSyncs = new ConcurrentQueue<int>();
        #endregion

        #region Constructors
        public ParsecWindow()
        {
            InitializeComponent();//System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString();
            try
            {
                CommandBindings.Add(new CommandBinding(ApplicationCommands.Close, new ExecutedRoutedEventHandler(delegate(object sender, ExecutedRoutedEventArgs args) { Application.Current.Shutdown(); })));
                rdoThisFight.IsChecked = true;
                rdoThisFight.Checked += FightTypeChangedHandler;
                rdoAllFights.Checked += FightTypeChangedHandler;
                txtSyncMs.Visibility = ((App)Application.Current).IsPingVisible ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
                txtRaidMode.Visibility = ((App)Application.Current).IsPingVisible ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
                menuTimers.Visibility = ((App)Application.Current).AreTimersEnabled ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
                btnTimerPop.Visibility = ((App)Application.Current).AreTimersEnabled ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
                chkShowAll.IsChecked = true;
                txtVersion.Text = "v " + App.AppVersion;
            }
            catch (Exception ex2)
            {
                try
                {
                    ((App)Application.Current).HandleError(null, "Constructor Error", ex2, this, true);
                }
                catch
                {
                    //swallow error on logging
                }
            }
        }
        #endregion
        
        #region Timer Event Handlers
        private void LogUpdatedHandler(object sender, OnLogUpdatedEventArgs e)
        {
            try
            {
                ProcessTimers();
                if (DateTime.UtcNow.CompareTo(NextTimeSyncAttempt) > 0)
                {
                    NextTimeSyncAttempt = DateTime.UtcNow.AddMinutes(App.TimeSyncRefreshInterval);
                    if (((App)Application.Current).IsTimeServerRunning())
                    {
                        ((App)Application.Current).UpdateTimeSync(true);
                        //display if there is a problem
                        if (((App)Application.Current).TimeSyncEnabled && !((App)Application.Current).IsTimeServerRunning())
                        {
                            this.HandleNoPopError("Time Sync Error", "Resynchronizing with the Time Server failed. Windows may have taken control of the time server connection. Go to File > Settings > Time Sync and click Refresh Sync to attempt to fix the problem.", null, null, this, false);
                        }
                    }
                }
                if (e.NewLines.Count > 0)
                {
                    //Debug.WriteLine(string.Format("{0}: Log Read", DateTime.Now.ToString("h:mm:ss.fff")));
                    ConsecutiveReadsWithNoData = 0;
                    bool containsCombatStart = false;
                    List<CombatLogEvent> newEvents = Log.AddLines(e.NewLines, out containsCombatStart);
                    if (containsCombatStart)
                    {
                        BindFightList(!IsLastFightSelected, Log.LastFightIndex, false);
                        BindRaidDetails(null);
                        if (((App)Application.Current).IsArchiveEnabled)
                        {
                            int archiveIndex = Log.LastFightIndex - 1;
                            CombatDetails archiveFight = Log.GetFight(archiveIndex);
                            if (archiveFight != null)
                            {
                                var raidDetails = archiveFight.GetRaidDetails();
                                if (raidDetails.Count > 0)
                                {
                                    //archive raid
                                    List<ArchivedRaidFightMember> fmem = raidDetails.Select(x => ArchivedRaidFightMember.ConvertToArchivedRaidFightItem(x)).ToList();
                                    ArchivedRaidFight.ArchiveRaidData(Log.FileName, new ArchivedRaidFight()
                                    {
                                        FightIndex = archiveIndex,
                                        FightStart = archiveFight.StartCombat,
                                        FightMembers = fmem
                                    });
                                }
                            }
                        }
                    }
                    var newCombatEvents = newEvents.Where(x => x.FightIndex == Log.LastFightIndex).ToList();
                    if (((App)Application.Current).AreTimersEnabled && newEvents.Count > 0)
                    {
                        var timerDefs = ((App)Application.Current).GetTimerDefinitions();
                        if (timerDefs.Count(x => x.IsEnabled) > 0)
                        {
                            foreach (CombatLogEvent nev in newEvents)
                            {
                                List<ActiveTimer> ats = timerDefs.Where(x => x.IsEnabled && x.TestTimerDefinition(nev)).Select(x => x.GetActiveTimer(nev.TimeStamp, ((App)Application.Current).GetCurrentServerTime(), nev.Target)).ToList();
                                if (ats.Count > 0)
                                {
                                    //ats.ForEach(x => { Debug.WriteLine(string.Format("Timer: {0} Initiated @ {1}", x.Description, x.StartTime.ToString("h:mm:ss fff"))); });
                                    List<string> removeNames = ats.Where(x => x.MultiTimerMode == MultipleTimerMode.UpdateTimerOnRepeat).Select(x => x.TriggerId).ToList();
                                    var timers = GetActiveTimers();
                                    List<string> ignoreNames = timers.Where(x => x.MultiTimerMode == MultipleTimerMode.IgnoreRepeatTimers).Select(x => x.TriggerId).ToList();
                                    ats = ats.Where(x => !ignoreNames.Contains(x.TriggerId)).ToList();
                                    foreach (var rn in removeNames)
                                    {
                                        RemoveActiveTimer(rn);
                                    }
                                    foreach (var a in ats)
                                    {
                                        AddActiveTimer(a);
                                    }
                                }
                            }
                        }
                    }
                    ProcessTimers();
                    UpdateCharacterName(false);
                    if (Log.LastFightIndex > 0 && newCombatEvents.Count > 0)
                    {
                        //if fight ended and none started update last time
                        if (IsLastFightSelected)
                        {
                            //update interface if currentFight is selected
                            CombatDetails currentFight = Log.GetFight(Log.LastFightIndex);
                            BindPopOuts(currentFight, newCombatEvents);
                            UpdateStats(currentFight, Log.LogStatistics);
                            BindFightTabs(currentFight);
                            BindCombatLog(newCombatEvents, containsCombatStart);
                            BindThreatMeter(currentFight);
                        }
                        else
                        {
                            BindPopOuts(null, newEvents);
                        }
                    }
                    ConsecutiveLogReadErrors = 0;
                }
                else
                {
                    //pulse with no lines
                    if (ConsecutiveReadsWithNoData > (MaxConsecutiveMsWithNoData / FilePollingRate))
                        Log.EndCurrentFight();
                    else
                        ConsecutiveReadsWithNoData++;
                }
                UpdateActiveTimers();
                //Debug.WriteLine(string.Format("Log Updated {0} lines @ {1}", e.NewLines.Count, DateTime.Now.ToString("h:mm:ss fff")));
                if (DateTime.Now.Subtract(LastMovePopOutsToFront).TotalMilliseconds > MovePopOutsToFrontMilliseconds)
                {
                    MovePopOutsToFront();
                    LastMovePopOutsToFront = DateTime.Now;
                    //Debug.WriteLine(string.Format("Moved popOuts @ {0}", DateTime.Now.ToString("h:mm:ss fff")));
                }
            }
            catch (Exception ex)
            {
                ConsecutiveLogReadErrors++;
                if (ConsecutiveLogReadErrors > MaxConsecutiveLogReadErrors)
                {
                    btnWatch_Click(new object(), new RoutedEventArgs()); //stop watching
                    ((App)Application.Current).HandleError("Parsec is experiencing errors parsing your log file", string.Format("Error on LogUpdatedHandler consecutive:{0}", ConsecutiveLogReadErrors), ex, this, true);
                }
                else
                {
                    ((App)Application.Current).HandleError(null, string.Format("Error on LogUpdatedHandler consecutive:{0}", ConsecutiveLogReadErrors), ex, this, true);
                }
            }
        }
        private void RaidUpdateTick(object sender, EventArgs e)
        {
            if (IsRaidConnected)
            {
                try
                {
                    if (!string.IsNullOrEmpty(Log.CharacterName))
                    {
                        if (DateTime.UtcNow.Subtract(LastCombatRaidSync).TotalSeconds > RaidTimeoutSeconds)
                        {
                            //disconnect
                            DisconnectFromRaid(Log.CharacterName, true);
                            SetDisconnected();
                            HandleNoPopError("Connection Error", "You have been disconnected from the raid due to inactivity", null, null, this, false);
                        }
                        if (Log.IsInCombat)
                            ConsecutiveOutOfCombatRaidSyncs = 0;
                        if (ConsecutiveOutOfCombatRaidSyncs < MaxOutOfCombatRaidSyncs)
                        {
                            ConsecutiveOutOfCombatRaidSyncs++;
                            //if (RaidUpdateMode != RaidUpdateModes.Active)
                            //    Debug.WriteLine(string.Format("ACTIVE {0}", DateTime.Now.ToString("h:mm:ss.fff")));
                            RaidUpdateMode = RaidUpdateModes.Active;
                            if (ConsecutiveRaidSyncTimeoutErrors < 2 || ConsecutiveRaidSyncErrors < 2)
                                UpdateRaidMode("Active", false);
                            else
                                UpdateRaidMode("Reconnecting", true);

                        }
                        else if (RaidLastUpdatedDate.HasValue && ConsecutiveRaidHeartBeatWithNoUpdate < MaxRaidSyncWithNoUpdate)
                        {
                            //if (RaidUpdateMode != RaidUpdateModes.Passive)
                            //    Debug.WriteLine(string.Format("PASSIVE {0}", DateTime.Now.ToString("h:mm:ss.fff")));
                            RaidUpdateMode = RaidUpdateModes.Passive;
                            UpdateRaidMode("Passive", false);
                        }
                        else if (LastRaidSyncDate.AddSeconds(RaidUpdateHeartBeatSeconds).CompareTo(DateTime.UtcNow) <= 0)
                        {
                            RaidUpdateMode = RaidUpdateModes.SleepSync;
                            UpdateRaidMode("Sleep Sync", false);
                        }
                        else
                        {
                            if (RaidUpdateMode == RaidUpdateModes.Active || RaidUpdateMode == RaidUpdateModes.Passive)
                            {
                                //ArchiveFight
                                int archiveIndex = Log.LastFightIndex;
                                CombatDetails archiveFight = Log.GetFight(archiveIndex);
                                if (archiveFight != null)
                                {
                                    List<ArchivedRaidFightMember> fmem = archiveFight.GetRaidDetails().Select(x => ArchivedRaidFightMember.ConvertToArchivedRaidFightItem(x)).ToList();
                                    ArchivedRaidFight.ArchiveRaidData(Log.FileName, new ArchivedRaidFight()
                                    {
                                        FightIndex = archiveIndex,
                                        FightStart = archiveFight.StartCombat,
                                        FightMembers = fmem
                                    });
                                }
                            }
                            RaidUpdateMode = RaidUpdateModes.Sleep;
                            UpdateRaidMode("Sleep", false);
                        }
                        if (RaidUpdateMode != RaidUpdateModes.Sleep)
                        {
                            //DateTime networkHit = DateTime.Now;
                            CombatDetails currentFight = new CombatDetails();
                            if (Log.LastFightIndex > 0)
                            {
                                currentFight = Log.GetFight(Log.LastFightIndex);
                            }
                            int requestedPollRate = (int)LogReader.NetworkPollMilliseconds;
                            SendRaidServerHeartBeat(currentFight, RaidUpdateMode == RaidUpdateModes.Active, ref requestedPollRate);
                            UpdatePing();
                            if (Log.LastFightIndex > 0 && RaidUpdateMode != RaidUpdateModes.SleepSync)
                            {
                                if (IsLastFightSelected)
                                {
                                    BindRaidDetails(currentFight);
                                }
                            }
                            BindConnectedRaiders(!IsLastFightSelected);
                            ConsecutiveRaidSyncErrors = 0;
                            ConsecutiveRaidSyncTimeoutErrors = 0;
                            int optimalPollRate = ParsecClientHelpers.FindOptimalPollRate(Log.LastLocalReadTime, requestedPollRate, 3000, .5, 100, 100);
                            //Console.WriteLine(string.Format("{0}: Network Poll - Recommend ({1} MS) @: {2}", networkHit.ToString("hh:mm:ss.fff"), optimalPollRate, DateTime.Now.AddMilliseconds(optimalPollRate).ToString("hh:mm:ss.fff")));
                            LogReader.NetworkPollMilliseconds = optimalPollRate;
                        }
                    }
                    else
                    {
                        if (DateTime.UtcNow.Subtract(LastCombatRaidSync).TotalSeconds > RaidTimeoutSeconds)
                        {
                            DisconnectFromRaid(null, true);
                            SetDisconnected();
                            HandleNoPopError("Connection Error", "You have been disconnected from the raid due to inactivity", null, null, this, false);
                        }
                        else
                        {
                            if (RaidUpdateMode == RaidUpdateModes.Active || RaidUpdateMode == RaidUpdateModes.Passive)
                            {
                                //ArchiveFight
                                int archiveIndex = Log.LastFightIndex;
                                CombatDetails archiveFight = Log.GetFight(archiveIndex);
                                if (archiveFight != null)
                                {
                                    List<ArchivedRaidFightMember> fmem = archiveFight.GetRaidDetails().Select(x => ArchivedRaidFightMember.ConvertToArchivedRaidFightItem(x)).ToList();
                                    ArchivedRaidFight.ArchiveRaidData(Log.FileName, new ArchivedRaidFight()
                                    {
                                        FightIndex = archiveIndex,
                                        FightStart = archiveFight.StartCombat,
                                        FightMembers = fmem
                                    });
                                }
                            }
                            RaidUpdateMode = RaidUpdateModes.Sleep;
                            UpdateRaidMode("No Character", false);
                        }
                    }
                }
                catch (TimeoutException tex)
                {
                    ConsecutiveRaidSyncTimeoutErrors++;
                    if (ConsecutiveRaidSyncTimeoutErrors >= MaxConsecutiveRaidSyncTimeoutErrors)
                    {
                        //disconnect
                        DisconnectFromRaid(null, false);
                        SetDisconnected();
                        HandleNoPopError("Connection Error", "The Parsec Raid Service has timed out. Click Connect to try again.", string.Format("Error on RaidTimer_Elapsed consecutive:{0}", ConsecutiveRaidSyncTimeoutErrors), tex, this, true);
                    }
                    else
                    {
                        ((App)Application.Current).HandleError(null, string.Format("Timeout Error on RaidUpdateTick consecutive:{0}", ConsecutiveRaidSyncTimeoutErrors), tex, this, false);
                    }
                }
                catch (System.ServiceModel.ServerTooBusyException sbex)
                {
                    ConsecutiveRaidSyncTimeoutErrors++;
                    if (ConsecutiveRaidSyncTimeoutErrors >= MaxConsecutiveRaidSyncTimeoutErrors)
                    {
                        //disconnect
                        DisconnectFromRaid(null, false);
                        SetDisconnected();
                        HandleNoPopError("Connection Error", "The Parsec Raid Service has timed out. Click Connect to try again.", string.Format("Error on RaidTimer_Elapsed consecutive:{0}", ConsecutiveRaidSyncTimeoutErrors), sbex, this, true);
                    }
                    else
                    {
                        ((App)Application.Current).HandleError(null, string.Format("Timeout Error on RaidUpdateTick consecutive:{0}", ConsecutiveRaidSyncTimeoutErrors), sbex, this, false);
                    }
                }
                catch (Exception ex)
                {
                    ConsecutiveRaidSyncErrors++;
                    if (ConsecutiveRaidSyncErrors >= MaxConsecutiveRaidSyncErrors)
                    {
                        DisconnectFromRaid(null, false);
                        SetDisconnected();
                        HandleNoPopError("Connection Error", "Parsec is experiencing errors connecting to the raid service", string.Format("Error on RaidTimer_Elapsed consecutive:{0}", ConsecutiveRaidSyncErrors), ex, this, true);
                    }
                    else
                    {
                        ((App)Application.Current).HandleError(null, string.Format("Error on RaidUpdateTick consecutive:{0}", ConsecutiveRaidSyncErrors), ex, this, true);
                    }
                }
            }
        }
        private void NewLogCreatedHandler(object sender, OnNewLogCreatedEventArgs e)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { NewLogCreatedHandler(sender, e); });
                return;
            }
            else
            {
                try
                {
                    //Debug.WriteLine("New Log Created Event Detected");
                    //stop watching
                    bool wasWatching = IsWatching;
                    bool wasRaidConnected = IsRaidConnected;
                    if (IsWatching)
                    {
                        StopWatching(); //stop Watching
                    }
                    if (IsRaidConnected)
                    {
                        //if you are raiding then stop
                        DisconnectFromRaid(Log.CharacterName, false);
                    }
                    //change filename
                    LogFileFullPath = e.FileName;
                    //if was watching start it again
                    if (wasWatching)
                    {
                        StartWatching(); //start Watching
                    }
                    if (wasRaidConnected)
                    {
                        IsRaidConnected = true;
                        LogReader.StartNetworkTimer();
                    }
                    ConsecutiveLogReadErrors = 0;
                }
                catch (Exception ex)
                {
                    ConsecutiveLogReadErrors++;
                    if (ConsecutiveLogReadErrors > MaxConsecutiveLogReadErrors)
                    {
                        btnWatch_Click(new object(), new RoutedEventArgs()); //stop watching
                        HandleNoPopError("Error Reading Log File", "Parsec is experiencing errors parsing your log file", string.Format("Error on NewLogCreatedHandler consecutive:{0}", ConsecutiveLogReadErrors), ex, this, true);
                    }
                    else
                    {
                        ((App)Application.Current).HandleError(null, string.Format("Error on NewLogCreatedHandler consecutive:{0}", ConsecutiveLogReadErrors), ex, this, true);
                    }
                }
            }
        }
        private void LogReaderError(object sender, ErrorEventArgs e)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { LogReaderError(sender, e); });
                return;
            }
            else
            {
                if (ConsecutiveLogReadErrors > MaxConsecutiveLogReadErrors)
                {
                    btnWatch_Click(new object(), new RoutedEventArgs()); //stop watching
                    HandleNoPopError("Error Reading Log File", "Parsec is experiencing errors parsing your log file", string.Format("Error on LogReaderError consecutive:{0}", ConsecutiveLogReadErrors), e.GetException(), this, true);
                }
                else
                {
                    ((App)Application.Current).HandleError(null, string.Format("Error on LogReaderError consecutive:{0}", ConsecutiveLogReadErrors), e.GetException(), this, true);
                }
            }
        }
        #endregion

        #region Button Event Handlers
        private void btnWatch_Click(object sender, RoutedEventArgs e)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { btnWatch_Click(sender, e); });
                return;
            }
            else
            {
                try
                {
                    //Debug.WriteLine("Watch button Clicked");
                    if (!IsWatching)
                    {
                        LogFileFullPath = RealTimeLogReader.FindNewestFile(LogFolderPath);
                        if (string.IsNullOrEmpty(LogFileFullPath))
                            throw new Exception("Log File Not Found");
                        if (string.IsNullOrEmpty(LogFolderPath))
                            throw new Exception("Log Folder Does not exist");
                        if (!Directory.Exists(LogFolderPath))
                            throw new Exception("Log Folder Does Not Exist");
                        if (!File.Exists(LogFileFullPath))
                            throw new Exception("Log Folder Does Not Exist");
                        StartWatching();
                        btnWatch.Visibility = System.Windows.Visibility.Collapsed;
                        btnStop.Visibility = System.Windows.Visibility.Visible;
                    }
                    else
                    {
                        StopWatching();
                        btnStop.Visibility = System.Windows.Visibility.Collapsed;
                        btnWatch.Visibility = System.Windows.Visibility.Visible;
                        if (IsRaidConnected)
                        {
                            DisconnectFromRaid(Log.CharacterName, true);
                            SetDisconnected();
                        }
                        //close all pops
                        CloseAllPopOuts();
                        DeselectAllTimers();
                    }
                }
                catch (Exception ex)
                {
                    ((App)Application.Current).HandleError("Error Watching File", "Error on btnWatch Click Event", ex, this, true);
                }
            }
        }
        private void btnConnect_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (string.IsNullOrEmpty(CurrentRaidGroup.Name))
                {
                    ((App)Application.Current).HandleError("You must select a Raid Group to join", null, null, this, false);
                }
                else
                {
                    if (!IsWatching)
                        btnWatch_Click(this, e);

                    if (IsWatching)
                    {
                        CombatDetails c = new CombatDetails();
                        bool isInCombat = false;
                        if (Log.LastFightIndex > 0)
                        {
                            c = Log.GetFight(Log.LastFightIndex);
                            isInCombat = Log.IsInCombat;
                        }
                        int requestedPollRate = 0;
                        LastTenSyncs = new ConcurrentQueue<int>();
                        ConsecutiveOutOfCombatRaidSyncs = MaxOutOfCombatRaidSyncs; //just connected so make sure we dont slip an active update in
                        ConsecutiveRaidHeartBeatWithNoUpdate = 0;
                        RaidLastUpdatedDate = null;
                        SendRaidServerHeartBeat(c, false, ref requestedPollRate);
                        LastCombatRaidSync = DateTime.UtcNow; //force last combat date to current date regardless of combat activity so we dont timeout
                        int li = 0;
                        LastTenSyncs.TryDequeue(out li);
                        UpdatePing();
                        txtRaidGroup.Text = CurrentRaidGroup.Name.Capitalize();
                        btnConnect.Visibility = System.Windows.Visibility.Collapsed;
                        btnDisconnect.Visibility = System.Windows.Visibility.Visible;
                        ((App)Application.Current).SaveSettings();
                        //go to raid tab
                        MyTabControl.SelectedIndex = 6;
                        if (isInCombat)
                            BindRaidDetails(c);
                        BindConnectedRaiders(!(IsLastFightSelected));
                        LogReader.StartNetworkTimer();
                        IsRaidConnected = true;
                        btnRefreshRaid.IsEnabled = true;
                        if (brdError.Visibility == System.Windows.Visibility.Visible && txtErrorHeader.Text.Contains("Connection Error"))
                        {
                            brdError.Visibility = System.Windows.Visibility.Collapsed;
                        }
                    }
                }
            }
            catch (TimeoutException ex)
            {
                ((App)Application.Current).HandleError("Raid Service Timed Out. Try Again Later.", "Error on btnConnect_Click", ex, this, false);
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error Connecting to Raid Service", "Error on btnConnect_Click", ex, this, true);
            }
        }
        private void btnDisconnect_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                DisconnectFromRaid(Log.CharacterName, true);
                SetDisconnected();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error Disconnecting", "Error on btnDisconnect_Click", ex, this, true);
            }
        }
        private void btnLoad_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                //Debug.WriteLine("Load Button Clicked");
                OpenFileDialog openFileDialog = new OpenFileDialog();
                openFileDialog.CustomPlaces.Add(new FileDialogCustomPlace(LogFolderPath));
                if (string.IsNullOrEmpty(LastOpenedLogFolder))
                    openFileDialog.InitialDirectory = LogFolderPath;
                else
                    openFileDialog.InitialDirectory = LastOpenedLogFolder;
                openFileDialog.Filter = "Text files (*.txt)|*.txt";
                bool? result = openFileDialog.ShowDialog();
                if (result.HasValue && result.Value)
                {
                    LastOpenedLogFolder = System.IO.Path.GetDirectoryName(openFileDialog.FileName);
                    //stop watching
                    if (IsWatching)
                    {
                        StopWatching();
                        btnStop.Visibility = System.Windows.Visibility.Collapsed;
                        btnWatch.Visibility = System.Windows.Visibility.Visible;
                        if (IsRaidConnected)
                        {
                            DisconnectFromRaid(Log.CharacterName, true);
                            SetDisconnected();
                        }
                    }
                    //change filename
                    LogFileFullPath = openFileDialog.FileName;
                    CurrentLogFileIsArchive = true;
                    ProcessLogFile();
                    ((App)Application.Current).AddRecentFile(LogFileFullPath);
                    BindRecentFiles();
                }
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error Loading File", "Error on btnLoad_Click", ex, this, true);
            }
        }
        private void btnRecentFile_Clicked(object sender, RoutedEventArgs e)
        {
            try
            {
                string fpath = (string)((MenuItem)sender).ToolTip;
                if (!string.IsNullOrEmpty(fpath))
                {
                    //stop watching
                    if (IsWatching)
                    {
                        StopWatching();
                        btnStop.Visibility = System.Windows.Visibility.Collapsed;
                        btnWatch.Visibility = System.Windows.Visibility.Visible;
                        if (IsRaidConnected)
                        {
                            DisconnectFromRaid(Log.CharacterName, true);
                            SetDisconnected();
                        }
                    }
                    //change filename
                    LogFileFullPath = fpath;
                    CurrentLogFileIsArchive = true;
                    ProcessLogFile();
                    ((App)Application.Current).AddRecentFile(LogFileFullPath);
                    BindRecentFiles();
                }
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error Loading File", "Error on btnLoad_Click", ex, this, true);
            }
        }
        private void btnApplyFilter_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                //Debug.WriteLine("Apply Filter Button Clicked");
                LogFilterParameters = new CombatLogFilterParameters()
                {
                    ShowAll = chkShowAll.IsChecked.Value,
                    ShowAbilities = chkAbilities.IsChecked.Value,
                    ShowOthersEffects = chkOthersEffects.IsChecked.Value,
                    ShowIncomingDamage = chkIncomingDamage.IsChecked.Value,
                    ShowIncomingHeals = chkIncomingHeals.IsChecked.Value,
                    ShowCharacterEffects = chkCharactersEffects.IsChecked.Value,
                    ShowEvents = chkEvents.IsChecked.Value,
                    ShowOutgoingDamage = chkOutgoingDamage.IsChecked.Value,
                    ShowOutgoingHeals = chkOutgoingHeals.IsChecked.Value,
                    KeyWordFilter = txtKeyWord.Text
                };
                int newSelectedFightIndex = Convert.ToInt32(ddlFights.SelectedValue);
                if (newSelectedFightIndex > 0)
                {
                    CombatDetails fight = Log.GetFight(newSelectedFightIndex);
                    if (fight != null)
                    {
                        List<CombatLogEvent> existingEvents = fight.GetEvents();
                        BindCombatLog(existingEvents, true);
                    }
                }
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error Applying Filter", "Error on btnApplyFilter_Click", ex, this, true);
            }
        }
        private void btnClearKeyWord_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                txtKeyWord.Text = "";
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on btnClearKeyWord_Click", ex, this, true);
            }
        }
        private void btnFilters_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (pnlFilters.Visibility == System.Windows.Visibility.Collapsed)
                {
                    pnlFilters.Visibility = System.Windows.Visibility.Visible;
                    btnFilterExpand.Visibility = System.Windows.Visibility.Collapsed;
                    btnFilterCollapse.Visibility = System.Windows.Visibility.Visible;
                }
                else
                {
                    pnlFilters.Visibility = System.Windows.Visibility.Collapsed;
                    btnFilterExpand.Visibility = System.Windows.Visibility.Visible;
                    btnFilterCollapse.Visibility = System.Windows.Visibility.Collapsed;
                }
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error Changing Filter", "Error on btnFilters_Click", ex, this, true);
            }
        }
        private void btnClearFindWord_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                txtFindWord.Text = "";
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on btnClearFindWord_Click", ex, this, true);
            }
        }
        private void btnFind_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                this.MyCombatLogControl.Find(txtFindWord.Text);
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on btnFind_Click", ex, this, true);
            }
        }
        private void btnFindNext_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                this.MyCombatLogControl.FindNext(txtFindWord.Text);
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on btnFindNext_Click", ex, this, true);
            }
        }
        private void btnFindPrevious_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                this.MyCombatLogControl.FindPrevious(txtFindWord.Text);
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on btnFindPrevious_Click", ex, this, true);
            }
        }
        private void btnExit_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                Application.Current.Shutdown();
            }
            catch (Exception ex)
            {
                Logger.WriteError("Error on ShutDown", ex);
                ((App)Application.Current).HandleError(null, "Error on ShutDown", ex, this, true);
            }
        }
        private void btnSettings_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                ((App)Application.Current).SaveSettings();
                SettingsWindow s = new SettingsWindow();
                s.Owner = this;
                s.ShowDialog();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on btnSettings_Click", ex, this, true);
            }
        }
        private void btnBackup_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (IsWatching)
                {
                    StopWatching();
                    btnStop.Visibility = System.Windows.Visibility.Collapsed;
                    btnWatch.Visibility = System.Windows.Visibility.Visible;
                    if (IsRaidConnected)
                    {
                        DisconnectFromRaid(Log.CharacterName, true);
                        SetDisconnected();
                    }
                }
                BackupWindow s = new BackupWindow();
                s.Owner = this;
                s.ShowDialog();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on btnBackup_Click", ex, this, true);
            }
        }
        private void btnRestore_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (IsWatching)
                {
                    StopWatching();
                    btnStop.Visibility = System.Windows.Visibility.Collapsed;
                    btnWatch.Visibility = System.Windows.Visibility.Visible;
                    if (IsRaidConnected)
                    {
                        DisconnectFromRaid(Log.CharacterName, true);
                        SetDisconnected();
                    }
                }
                ImportWindow s = new ImportWindow();
                s.Owner = this;
                s.ShowDialog();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on btnRestore_Click", ex, this, true);
            }
        }
        private void btnStats_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (btnStats.IsChecked)
                {
                    CombatDetails currentFight = null;
                    if (Log != null && Log.LastFightIndex > 0)
                        currentFight = Log.GetFight(Log.LastFightIndex);
                    ((App)Application.Current).StatPopOut.ShowActivated = false;
                    ((App)Application.Current).StatPopOut.SetStats(currentFight, AbsorbShieldValue);
                    ((App)Application.Current).StatPopOut.Show();
                    ((App)Application.Current).StatPopOut.Owner = ((App)Application.Current).PopUpHost;
                    ((App)Application.Current).StatPopOut.Topmost = true;
                }
                else
                    ((App)Application.Current).StatPopOut.Hide();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error Toggling Stat Overview", "Error on btnStats_Click", ex, this, true);
            }
        }
        private void btnRaidDps_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (btnRaidDps.IsChecked)
                {
                    CombatDetails currentFight = null;
                    if (Log != null && Log.LastFightIndex > 0)
                        currentFight = Log.GetFight(Log.LastFightIndex);
                    if (currentFight != null)
                    {
                        BindRaidDetails(currentFight);
                    }
                    else
                        ((App)Application.Current).RaidDpsPopOut.BindData(null, null);
                    ((App)Application.Current).RaidDpsPopOut.ShowActivated = false;
                    ((App)Application.Current).RaidDpsPopOut.Show();
                    ((App)Application.Current).RaidDpsPopOut.Owner = ((App)Application.Current).PopUpHost;
                    ((App)Application.Current).RaidDpsPopOut.Topmost = true;
                }
                else
                    ((App)Application.Current).RaidDpsPopOut.Hide();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error Toggling Raid DPS", "Error on btnRaidDps_Click", ex, this, true);
            }
        }
        private void btnTimerPop_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (btnTimerPop.IsChecked)
                {
                    ((App)Application.Current).TimerPopOut.ShowActivated = false;
                    ((App)Application.Current).TimerPopOut.Show();
                    ((App)Application.Current).TimerPopOut.Owner = ((App)Application.Current).PopUpHost;
                    ((App)Application.Current).TimerPopOut.Topmost = true;
                }
                else
                    ((App)Application.Current).TimerPopOut.Hide();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error Toggling Timer Pop Out", "Error on btnTimerPop_Click", ex, this, true);
            }
        }
        private void btnDpsMeter_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (btnDpsMeter.IsChecked)
                {
                    CombatDetails currentFight = null;
                    if (Log != null && Log.LastFightIndex > 0)
                        currentFight = Log.GetFight(Log.LastFightIndex);
                    ((App)Application.Current).DpsMeterPopOut.ShowActivated = false;
                    ((App)Application.Current).DpsMeterPopOut.BindData(currentFight);
                    ((App)Application.Current).DpsMeterPopOut.Show();
                    ((App)Application.Current).DpsMeterPopOut.Owner = ((App)Application.Current).PopUpHost;
                    ((App)Application.Current).DpsMeterPopOut.Topmost = true;
                }
                else
                    ((App)Application.Current).DpsMeterPopOut.Hide();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error Toggling Stat Overview", "Error on btnStats_Click", ex, this, true);
            }
        }
        private void btnRaidHps_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (btnRaidHps.IsChecked)
                {
                    CombatDetails currentFight = null;
                    if (Log != null && Log.LastFightIndex > 0)
                        currentFight = Log.GetFight(Log.LastFightIndex);
                    if (currentFight != null)
                    {
                        BindRaidDetails(currentFight);
                    }
                    else
                        ((App)Application.Current).RaidHpsPopOut.BindData(null, null);
                    ((App)Application.Current).RaidHpsPopOut.ShowActivated = false;
                    ((App)Application.Current).RaidHpsPopOut.Show();
                    ((App)Application.Current).RaidHpsPopOut.Owner = ((App)Application.Current).PopUpHost;
                    ((App)Application.Current).RaidHpsPopOut.Topmost = true;
                }
                else
                    ((App)Application.Current).RaidHpsPopOut.Hide();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error Toggling Raid HPS", "Error on btnRaidHps_Click", ex, this, true);
            }
        }
        private void btnRaidTps_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (btnRaidTps.IsChecked)
                {
                    CombatDetails currentFight = null;
                    if (Log != null && Log.LastFightIndex > 0)
                        currentFight = Log.GetFight(Log.LastFightIndex);
                    if (currentFight != null)
                    {
                        BindRaidDetails(currentFight);
                    }
                    else
                        ((App)Application.Current).RaidTpsPopOut.BindData(null);
                    ((App)Application.Current).RaidTpsPopOut.ShowActivated = false;
                    ((App)Application.Current).RaidTpsPopOut.Show();
                    ((App)Application.Current).RaidTpsPopOut.Owner = ((App)Application.Current).PopUpHost;
                    ((App)Application.Current).RaidTpsPopOut.Topmost = true;
                    //menuPopOuts.Focus();
                    //menuPopOuts.IsSubmenuOpen = true;
                }
                else
                    ((App)Application.Current).RaidTpsPopOut.Hide();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error Toggling Raid Threat", "Error on btnRaidTps_Click", ex, this, true);
            }
        }
        private void btnStatStopWatch_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (btnStatStopWatch.IsChecked)
                {
                    ((App)Application.Current).StatStopWatchPopOut.ShowActivated = false;
                    ((App)Application.Current).StatStopWatchPopOut.Show();
                    ((App)Application.Current).StatStopWatchPopOut.Owner = ((App)Application.Current).PopUpHost;
                    ((App)Application.Current).StatStopWatchPopOut.Topmost = true;
                }
                else
                    ((App)Application.Current).StatStopWatchPopOut.Hide();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error Toggling Stopwatch", "Error on btnRaidTps_Click", ex, this, true);
            }
        }
        private void btnMinimize_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                WindowState = WindowState.Minimized;
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError(null, "Error on btnMinimize_Click", ex, this, true);
            }
        }
        private void btnRaidGroup_Clicked(object sender, RoutedEventArgs e)
        {
            MenuItem m = ((MenuItem)sender);
            if (m.IsChecked)
            {
                object o = m.Header;
                if (o != null)
                {
                    m.IsChecked = true;
                    foreach (object oi in menuRaidGroups.Items)
                    {
                        if (oi is MenuItem)
                        {
                            MenuItem i = (MenuItem)oi;
                            if (((string)i.Header) != "Manage")
                            {
                                if (i != m)
                                {
                                    i.IsChecked = false;
                                }
                            }
                        }
                    }
                    string groupName = (string)o;
                    RaidGroup c = ((App)Application.Current).RaidGroups.FirstOrDefault(x => string.Compare(x.Name, groupName, true) == 0);
                    if (c != null)
                    {
                        bool wasRaiding = IsRaidConnected;
                        if (IsRaidConnected)
                        {
                            //if you are raiding then stop
                            DisconnectFromRaid(Log.CharacterName, true);
                            SetDisconnected();
                        }
                        CurrentRaidGroup = c;
                        if (wasRaiding)
                        {
                            //if you were raiding reconnect
                            btnConnect_Click(btnConnect, new RoutedEventArgs());
                        }
                    }
                }
            }
            else
            {
                m.IsChecked = true;
            }
        }
        private void btnManageGroups_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                ((App)Application.Current).SaveSettings();
                ManageRaidGroups s = new ManageRaidGroups();
                s.Owner = this;
                s.ShowDialog();
                BindRaidGroups();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on btnManageGroups_Click", ex, this, true);
            }
        }
        private void btnHelp_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                AboutWindow s = new AboutWindow();
                s.Owner = this;
                s.ShowDialog();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on btnHelp_Click", ex, this, true);
            }
        }
        private void btnRefreshRaid_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (IsRaidConnected)
                {
                    CombatDetails currentFight = new CombatDetails();
                    if (Log.LastFightIndex > 0)
                    {
                        currentFight = Log.GetFight(Log.LastFightIndex);
                    }
                    int requestedPollRate = 0;
                    SendRaidServerHeartBeat(currentFight, false, ref requestedPollRate);
                    UpdatePing();
                    if (Log.LastFightIndex > 0)
                    {
                        if (IsLastFightSelected)
                        {
                            BindRaidDetails(currentFight);
                        }
                    }
                    BindConnectedRaiders(!IsLastFightSelected);
                    ConsecutiveRaidSyncErrors = 0;
                    ConsecutiveRaidSyncTimeoutErrors = 0;
                }
            }
            catch (TimeoutException tex)
            {
                DisconnectFromRaid(null, false);
                SetDisconnected();
                ((App)Application.Current).HandleError("The Parsec Raid Service has timed out. Click Connect to try again.", "Error on btnRefreshRaid_Click", tex, this, false);
            }
            catch (System.ServiceModel.ServerTooBusyException sbex)
            {
                DisconnectFromRaid(null, false);
                SetDisconnected();
                ((App)Application.Current).HandleError("The Parsec Raid Service has timed out. Click Connect to try again.", "Error on btnRefreshRaid_Click", sbex, this, false);
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on btnRefreshRaid_Click", ex, this, true);
            }
        }
        private void btnManageTimers_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                ((App)Application.Current).SaveSettings();
                ManageTimers s = new ManageTimers();
                s.Owner = this;
                s.ShowDialog();
                BindTimerDefinitions();
                ((App)Application.Current).SaveSettings();
                if (Log != null)
                    ((App)Application.Current).SaveCharacterSettings(Log.CharacterName);
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on btnManageTimers_Click", ex, this, true);
            }
        }
        private void btnClearTimers_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                ClearActiveTimers();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on btnManageTimers_Click", ex, this, true);
            }
        }
        private void btnTimerEnable_Clicked(object sender, RoutedEventArgs e)
        {
            MenuItem m = ((MenuItem)sender);
            object o = m.Header;
            if (o != null)
            {
                string name = (string)o;
                TimerDefinition c = ((App)Application.Current).GetTimerDefinition(name);
                if (c != null)
                {
                    c.IsEnabled = m.IsChecked;
                }
                //remove timer if in activetimer
                RemoveActiveTimer(c.Description);
            }
            if (Log != null)
                ((App)Application.Current).SaveCharacterSettings(Log.CharacterName);
        }
        private void btnVisitSite_Click(object sender, RoutedEventArgs e)
        {
            Process.Start(new ProcessStartInfo("http://www.ParsecParser.com"));
            e.Handled = true;
        }
        private void btnShareFight_Click(object sender, RoutedEventArgs e)
        {
            int newSelectedFightIndex = Convert.ToInt32(ddlFights.SelectedValue);
            if (newSelectedFightIndex > 0)
            {
                CombatDetails currentFight = Log.GetFight(newSelectedFightIndex);
                if (currentFight != null)
                {
                    ShareFightWindow sfw = new ShareFightWindow();
                    sfw.CurrentFight = currentFight;
                    sfw.Owner = this;
                    sfw.ShowDialog();
                }
            }
        }
        private void btnReleaseNotes_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                ShowNotes();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on btnReleaseNotes_Click", ex, this, true);
            }
        }
        private void btnErrorMoreInfo_Click(object sender, RoutedEventArgs e)
        {
            popErrorInfo.IsOpen = true;
        }
        private void btnErrorClose_Click(object sender, RoutedEventArgs e)
        {
            brdError.Visibility = System.Windows.Visibility.Collapsed;
        }

        private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
        {
            Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
            e.Handled = true;
        }
        #endregion

        #region DropDown Event Handlers
        private void ddlFights_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            try
            {
                if (ddlFights.SelectedValue != null)
                {
                    int newSelectedFightIndex = Convert.ToInt32(ddlFights.SelectedValue);
                    int lastPossibleFightIndex = ddlFights.Items.Cast<FightDisplayItem>().Select(x => x.FightIndex).Max();
                    IsLastFightSelected = newSelectedFightIndex == lastPossibleFightIndex;
                    if (newSelectedFightIndex > 0)
                    {
                        CombatDetails currentFight = Log.GetFight(newSelectedFightIndex);
                        UpdateStats(currentFight, Log.LogStatistics);
                        BindPopOuts(currentFight, null);
                        BindFightTabs(currentFight);
                        List<CombatLogEvent> existingEvents = currentFight.GetEvents();
                        BindCombatLog(existingEvents, true);
                        BindRaidDetails(currentFight);
                        BindConnectedRaiders(!(IsLastFightSelected && IsWatching));
                    }
                }
                else
                {
                    IsLastFightSelected = true;
                }
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error Loading Fight", "Error on ddlFights_SelectionChanged", ex, this, true);
            }
            e.Handled = true;
        }
        private void ddlRaidReportType_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            try
            {
                int selectedFightIndex = Convert.ToInt32(ddlFights.SelectedValue);
                if (selectedFightIndex > 0)
                {
                    CombatDetails c = Log.GetFight(selectedFightIndex);
                    BindRaidDetails(c);
                }
                e.Handled = true;
                //SaveSettings();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on ddlRaidReportType_SelectionChanged", ex, this, true);
            }
            e.Handled = true;
        }
        bool IgnoreDamageOutSelectionChanged = false;
        private void ddlDamageOutTargets_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            try
            {
                if (IgnoreDamageOutSelectionChanged == false)
                {
                    int selectedFightIndex = Convert.ToInt32(ddlFights.SelectedValue);
                    if (selectedFightIndex > 0)
                    {
                        CombatDetails c = Log.GetFight(selectedFightIndex);
                        BindFightTabs(c);
                    }
                }
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on ddlDamageOutTargets_SelectionChanged", ex, this, true);
            }
            e.Handled = true;
        }
        #endregion

        #region Checkbox Event Handlers
        private void chkShowAll_Checked(object sender, RoutedEventArgs e)
        {
            try
            {
                chkOthersEffects.IsChecked = false;
                chkAbilities.IsChecked = false;
                chkIncomingDamage.IsChecked = false;
                chkIncomingHeals.IsChecked = false;
                chkOutgoingDamage.IsChecked = false;
                chkCharactersEffects.IsChecked = false;
                chkEvents.IsChecked = false;
                chkOutgoingHeals.IsChecked = false;

                chkCharactersEffects.IsEnabled = false;
                chkAbilities.IsEnabled = false;
                chkIncomingDamage.IsEnabled = false;
                chkIncomingHeals.IsEnabled = false;
                chkOutgoingDamage.IsEnabled = false;
                chkOthersEffects.IsEnabled = false;
                chkEvents.IsEnabled = false;
                chkOutgoingHeals.IsEnabled = false;
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on chkShowAll_Checked", ex, this, true);
            }
        }
        private void chkShowAll_Unchecked(object sender, RoutedEventArgs e)
        {
            try
            {
                chkOthersEffects.IsEnabled = true;
                chkAbilities.IsEnabled = true;
                chkIncomingDamage.IsEnabled = true;
                chkIncomingHeals.IsEnabled = true;
                chkOutgoingDamage.IsEnabled = true;
                chkCharactersEffects.IsEnabled = true;
                chkEvents.IsEnabled = true;
                chkOutgoingHeals.IsEnabled = true;
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on chkShowAll_Unchecked", ex, this, true);
            }
        }
        private void ThreatTypeCheckChanged(object sender, RoutedEventArgs e)
        {
            try
            {
                int selectedFightIndex = Convert.ToInt32(ddlFights.SelectedValue);
                if (selectedFightIndex > 0)
                {
                    CombatDetails c = Log.GetFight(selectedFightIndex);
                    BindFightTabs(c);
                }
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on ThreatTypeCheckChanged", ex, this, true);
            }
        }
        private void DamageOutTypeCheckChanged(object sender, RoutedEventArgs e)
        {
            try
            {
                int selectedFightIndex = Convert.ToInt32(ddlFights.SelectedValue);
                if (selectedFightIndex > 0)
                {
                    CombatDetails c = Log.GetFight(selectedFightIndex);
                    BindFightTabs(c);
                }
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on DamageOutTypeCheckChanged", ex, this, true);
            }
        }
        private void FightTypeChangedHandler(object sender, RoutedEventArgs e)
        {
            if (Log != null && Log.LastFightIndex > 0) //Fight found
            {
                CombatDetails currentFight = Log.GetFight(Log.LastFightIndex);
                UpdateStats(currentFight, Log.LogStatistics);
            }
        }
        #endregion

        #region Textbox Event Handlers
        private void txtKeyWord_KeyDown(object sender, KeyEventArgs e)
        {
            try
            {
                if (e.Key == Key.Return)
                {
                    btnApplyFilter_Click(sender, e);
                }
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on txtKeyWord_KeyDown", ex, this, true);
            }
        }
        private void txtFindWord_KeyDown(object sender, KeyEventArgs e)
        {
            try
            {
                if (e.Key == Key.Return)
                {
                    btnFind_Click(sender, e);
                }
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error", "Error on txtFindWord_KeyDown", ex, this, true);
            }
        }
        #endregion

        #region Other Event Handlers
        private void MyTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            try
            {
                int fightIndex = Convert.ToInt32(ddlFights.SelectedValue);
                if (fightIndex > 0)
                {
                    BindFightTabs(Log.GetFight(fightIndex));
                }
                e.Handled = true;
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error Changing Tabs", "Error on MyTabControl_SelectionChanged", ex, this, true);
            }
        }
        private void OpenLogFileInOS(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
        {
            Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
            e.Handled = true;
        }
        private void DragWindow(object sender, MouseButtonEventArgs args)
        {
            try
            {
                this.WindowState = WindowState.Normal;
                DragMove();
                args.Handled = true;
            }
            catch
            {
                //ignore
            }
        }
        private void Window_Deactivated(object sender, EventArgs e)
        {
            try
            {
                MovePopOutsToFront();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError(null, "Error on Window_Deactivated", ex, this, true);
            }
        }
        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            ((App)Application.Current).SaveSettings();
            StopWatching();
            IsRaidConnected = false;
            if (LogReader != null)
            {
                LogReader.Dispose();
                LogReader = null;
            }
            if (LogParser != null)
            {
                LogParser = null;
            }
            if (Log != null)
            {
                Log = null;
            }
            LogFilterParameters = null;
            CurrentRaidGroup = null;
        }
        private void Window_Closed(object sender, EventArgs e)
        {
            try
            {
                ((App)Application.Current).ShuttingDown();
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError(null, "Error on Window_Closed", ex, this, true);
            }
        }
        #endregion

        #region UI Update Methods
        private void UpdateRaidMode(string text, bool isWarning)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { UpdateRaidMode(text, isWarning); });
                return;
            }
            else
            {
                txtRaidMode.Text = text;
                if (isWarning)
                    txtRaidMode.Foreground = new SolidColorBrush(Colors.Red);
                else
                    txtRaidMode.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF666666"));
            }
        }
        private void HandleNoPopError(string errorHeader, string errorMessage, string logMessage, Exception ex, Window owner, bool showException)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { HandleNoPopError(errorHeader, errorMessage, logMessage, ex, owner, showException); });
                return;
            }
            else
            {
                try
                {
                    brdError.Visibility = System.Windows.Visibility.Visible;
                    txtErrorHeader.Text = errorHeader;
                    txtErrorMessage.Text = errorMessage;
                    btnErrorMoreInfo.Visibility = ex != null ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
                    txtErrorMoreInfo.Text = ex != null ? ex.Message : null;
                    if (logMessage != null)
                        ((App)Application.Current).HandleError(null, logMessage, ex, this, true);
                }
                catch (Exception ex2)
                {
                    Logger.WriteError("Critical Error on Exception", ex2);
                }
            }
        }
        public void UpdatePing()
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { UpdatePing(); });
                return;
            }
            else
            {
                if (LastTenSyncs.Count > 0)
                {
                    txtSyncMs.Text = "Ping " + Convert.ToInt32(LastTenSyncs.Average()).ToString() + " ms";
                    txtSyncMs.ToolTip = string.Join(Environment.NewLine, LastTenSyncs.Select(x => x + " ms").ToArray());
                }
                else
                {
                    txtSyncMs.Text = string.Empty;
                    txtSyncMs.ToolTip = string.Empty;
                }
            }
        }
        private void SetDisconnected()
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { SetDisconnected(); });
                return;
            }
            else
            {
                UpdateRaidMode(string.Empty, false);
                btnRefreshRaid.IsEnabled = false;
                btnConnect.Visibility = System.Windows.Visibility.Visible;
                btnDisconnect.Visibility = System.Windows.Visibility.Collapsed;
            }
        }
        private void MovePopOutsToFront()
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { MovePopOutsToFront(); });
                return;
            }
            else
            {
                if (this.btnStatStopWatch.IsChecked)
                {
                    ((App)Application.Current).StatStopWatchPopOut.Topmost = true;
                    MoveToFront.MoveWindowToFront(((App)Application.Current).StatStopWatchPopOut.Title);
                }
                if (this.btnStats.IsChecked)
                {
                    ((App)Application.Current).StatPopOut.Topmost = true;
                    MoveToFront.MoveWindowToFront(((App)Application.Current).StatPopOut.Title);
                }
                if (this.btnRaidDps.IsChecked)
                {
                    ((App)Application.Current).RaidDpsPopOut.Topmost = true;
                    MoveToFront.MoveWindowToFront(((App)Application.Current).RaidDpsPopOut.Title);
                }
                if (this.btnRaidHps.IsChecked)
                {
                    ((App)Application.Current).RaidHpsPopOut.Topmost = true;
                    MoveToFront.MoveWindowToFront(((App)Application.Current).RaidHpsPopOut.Title);
                }
                if (this.btnRaidTps.IsChecked)
                {
                    ((App)Application.Current).RaidTpsPopOut.Topmost = true;
                    MoveToFront.MoveWindowToFront(((App)Application.Current).RaidTpsPopOut.Title);
                }
                if (this.btnTimerPop.IsChecked && ((App)Application.Current).AreTimersEnabled)
                {
                    ((App)Application.Current).TimerPopOut.Topmost = true;
                    MoveToFront.MoveWindowToFront(((App)Application.Current).TimerPopOut.Title);
                }
                if (this.btnDpsMeter.IsChecked)
                {
                    ((App)Application.Current).DpsMeterPopOut.Topmost = true;
                    MoveToFront.MoveWindowToFront(((App)Application.Current).DpsMeterPopOut.Title);
                }
            }
        }
        private void SetArchiveMessage(System.Windows.Visibility vis, string dateText, string nameText, Uri linkUri)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { SetArchiveMessage(vis, dateText, nameText, linkUri); });
                return;
            }
            else
            {
                brdArchive.Visibility = vis;
                txtArchiveDate.Text = dateText;
                txtArchiveName.Text = nameText;
                if (linkUri != null)
                    lnkArchiveName.NavigateUri = linkUri;
            }
        }
        private void UpdateCharacterName(bool forceSettingsUpdate)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { UpdateCharacterName(forceSettingsUpdate); });
                return;
            }
            else
            {
                string characterName = "Character";
                if (Log != null && !string.IsNullOrEmpty(Log.CharacterName))
                {
                    characterName = Log.CharacterName;
                    if (forceSettingsUpdate || lblCharacterName.Text != characterName)
                        ((App)Application.Current).ApplyCharacterSettings(Log.CharacterName);
                }
                else if (Log != null && string.IsNullOrEmpty(Log.CharacterName))
                {
                    CloseAllPopOuts();
                    DeselectAllTimers();
                }
                lblCharacterName.Text = characterName;
            }
        }
        private void UpdateStats(CombatDetails fightDetails, CombatStatistics logStats)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { UpdateStats(fightDetails, logStats); });
                return;
            }
            else
            {
                if ((rdoThisFight.IsChecked ?? false) && fightDetails != null && fightDetails.CombatTimeSpan.TotalSeconds > 0)
                {
                    lblLengthValue.Text = String.Format("{0:mm\\:ss\\.fff}", fightDetails.CombatTimeSpan);

                    lblApmValue.Text = (fightDetails.Activates / fightDetails.CombatTimeSpan.TotalMinutes).ToString("N1");

                    lblDamageOutValue.Text = fightDetails.DamageOut.ToString("N0");
                    lblDamageOutPerSecValue.Text = (fightDetails.DamageOut / fightDetails.CombatTimeSpan.TotalSeconds).ToString("N0");

                    lblHealsOutPerSecValue.Text = (fightDetails.HealOut / fightDetails.CombatTimeSpan.TotalSeconds).ToString("N0");
                    lblHealsOutValue.Text = fightDetails.HealOut.ToString("N0");

                    if (AbsorbShieldValue > 0)
                    {
                        lblShield.Text = "Shielding Done";
                        lblShield.ToolTip = "Total Shielding | SPS";
                        lblShieldValue.ToolTip = "Total Shielding";
                        lblShieldPerSecValue.ToolTip = "Shielding Per Second";
                        lblShield.Foreground = ParsecClientHelpers.MainLabelColorBrush;
                        lblShieldValue.Foreground = ParsecClientHelpers.BlackBackground;
                        lblShieldPerSecValue.Foreground = ParsecClientHelpers.BlackBackground;
                        lblShieldValue.Text = (fightDetails.AbsorbShieldsCast * AbsorbShieldValue).ToString("N0");
                        lblShieldPerSecValue.Text = ((fightDetails.AbsorbShieldsCast * AbsorbShieldValue) / fightDetails.CombatTimeSpan.TotalSeconds).ToString("N0");
                    }
                    else
                    {
                        if (fightDetails.AbsorbShieldsCast > 0)
                            lblShield.Text = "Shielding Done (?)";
                        else
                            lblShield.Text = "Shielding Done";
                        lblShield.ToolTip = "Use File > Character Settings to set your Absorb Shield Value to see these stats";
                        lblShieldValue.ToolTip = lblShield.ToolTip;
                        lblShieldPerSecValue.ToolTip = lblShield.ToolTip;
                        lblShield.Foreground = ParsecClientHelpers.LightMainLabelColorBrush;
                        lblShieldValue.Foreground = ParsecClientHelpers.LightMainLabelColorBrush;
                        lblShieldPerSecValue.Foreground = ParsecClientHelpers.LightMainLabelColorBrush;
                        lblShieldValue.Text = "-";
                        lblShieldPerSecValue.Text = "-";
                    }

                    lblEffectHealPctValue.Text = (fightDetails.HealOut > 0 ? ((double)fightDetails.EffectiveHealOut / (double)fightDetails.HealOut) : 0).ToString("P1");
                    lblEffectHealPerSecValue.Text = (fightDetails.EffectiveHealOut / fightDetails.CombatTimeSpan.TotalSeconds).ToString("N0");

                    lblDamageTakenValue.Text = fightDetails.DamageIn.ToString("N0");
                    lblDamageTakenPerSecValue.Text = (fightDetails.DamageIn / fightDetails.CombatTimeSpan.TotalSeconds).ToString("N0");

                    lblHealsInValue.Text = fightDetails.HealIn.ToString("N0");
                    lblHealsInPerSecValue.Text = (fightDetails.HealIn / fightDetails.CombatTimeSpan.TotalSeconds).ToString("N0");

                    lblThreatValue.Text = fightDetails.Threat.ToString("N0");
                    lblThreatPerSecValue.Text = (fightDetails.Threat / fightDetails.CombatTimeSpan.TotalSeconds).ToString("N0");
                }
                else if ((rdoAllFights.IsChecked ?? false) && logStats != null && logStats.CombatTimeSpan.TotalSeconds > 0)
                {
                    lblLengthValue.Text = String.Format("{0:hh\\:mm\\:ss\\.fff}", logStats.CombatTimeSpan);

                    lblApmValue.Text = (logStats.Activates / logStats.CombatTimeSpan.TotalMinutes).ToString("N1");

                    lblDamageOutValue.Text = logStats.DamageOut.ToString("N0");
                    lblDamageOutPerSecValue.Text = (logStats.DamageOut / logStats.CombatTimeSpan.TotalSeconds).ToString("N0");

                    lblHealsOutValue.Text = logStats.HealOut.ToString("N0");
                    lblHealsOutPerSecValue.Text = (logStats.HealOut / logStats.CombatTimeSpan.TotalSeconds).ToString("N0");

                    lblShieldValue.Text = (logStats.AbsorbShieldsCast * AbsorbShieldValue).ToString("N0");
                    lblShieldPerSecValue.Text = ((logStats.AbsorbShieldsCast * AbsorbShieldValue) / logStats.CombatTimeSpan.TotalSeconds).ToString("N0");

                    lblEffectHealPctValue.Text = (logStats.HealOut > 0 ? ((double)logStats.EffectiveHealOut / (double)logStats.HealOut) : 0).ToString("P1");
                    lblEffectHealPerSecValue.Text = (logStats.EffectiveHealOut / logStats.CombatTimeSpan.TotalSeconds).ToString("N0");

                    lblDamageTakenValue.Text = logStats.DamageIn.ToString("N0");
                    lblDamageTakenPerSecValue.Text = (logStats.DamageIn / logStats.CombatTimeSpan.TotalSeconds).ToString("N0");

                    lblHealsInValue.Text = logStats.HealIn.ToString("N0");
                    lblHealsInPerSecValue.Text = (logStats.HealIn / logStats.CombatTimeSpan.TotalSeconds).ToString("N0");

                    lblThreatPerSecValue.Text = (logStats.Threat / logStats.CombatTimeSpan.TotalSeconds).ToString("N0");
                    lblThreatValue.Text = logStats.Threat.ToString("N0");
                }
                else
                {
                    lblLengthValue.Text = "";

                    lblApmValue.Text = "";

                    lblDamageOutValue.Text = "";
                    lblDamageOutPerSecValue.Text = "";

                    lblHealsOutValue.Text = "";
                    lblHealsOutPerSecValue.Text = "";

                    lblShieldValue.Text = "";
                    lblShieldPerSecValue.Text = "";

                    lblEffectHealPctValue.Text = "";
                    lblEffectHealPerSecValue.Text = "";

                    lblDamageTakenValue.Text = "";
                    lblDamageTakenPerSecValue.Text = "";

                    lblHealsInValue.Text = "";
                    lblHealsInPerSecValue.Text = "";

                    lblEffectHealPctValue.Text = "";
                    lblEffectHealPerSecValue.Text = "";

                    lblThreatPerSecValue.Text = "";
                    lblThreatValue.Text = "";
                }

                if (fightDetails != null && fightDetails.CombatTimeSpan.TotalSeconds > 0 && fightDetails.CompanionInFight)
                {
                    grdCompanion.Visibility = !fightDetails.CompanionInFight ? System.Windows.Visibility.Collapsed : System.Windows.Visibility.Visible;
                    txtCompanionName.Text = fightDetails.Companion != null ? fightDetails.Companion.DisplayName : "Companion";
                    lblCompDamageOutValue.Text = fightDetails.CompanionDamageOut.ToString("N0");
                    lblCompThreatValue.Text = fightDetails.CompanionThreat.ToString("N0");
                    lblCompHealsOutValue.Text = fightDetails.CompanionHealOut.ToString("N0");
                    lblCompDamageInValue.Text = fightDetails.CompanionDamageIn.ToString("N0");
                    lblCompHealsInValue.Text = fightDetails.CompanionHealIn.ToString("N0");
                    lblCompDamageOutValue2.Text = (fightDetails.CompanionDamageOut / fightDetails.CombatTimeSpan.TotalSeconds).ToString("N0");
                    lblCompThreatValue2.Text = (fightDetails.CompanionThreat / fightDetails.CombatTimeSpan.TotalSeconds).ToString("N0");
                    lblCompHealsOutValue2.Text = (fightDetails.CompanionHealOut / fightDetails.CombatTimeSpan.TotalSeconds).ToString("N0");
                    lblCompDamageInValue2.Text = (fightDetails.CompanionDamageIn / fightDetails.CombatTimeSpan.TotalSeconds).ToString("N0");
                    lblCompHealsInValue2.Text = (fightDetails.CompanionHealIn / fightDetails.CombatTimeSpan.TotalSeconds).ToString("N0");
                    lblCompHealsOutValue3.Text = (fightDetails.CompanionHealOut > 0 ? ((double)fightDetails.CompanionEffectiveHealOut / (double)fightDetails.CompanionHealOut) : 0).ToString("P1");
                    lblCompHealsOutValue4.Text = (fightDetails.CompanionEffectiveHealOut / fightDetails.CombatTimeSpan.TotalSeconds).ToString("N0");
                }
                else
                {
                    grdCompanion.Visibility = System.Windows.Visibility.Collapsed;
                    txtCompanionName.Text = "Companion";
                    lblCompDamageOutValue.Text = "";
                    lblCompThreatValue.Text = "";
                    lblCompHealsOutValue.Text = "";
                    lblCompDamageInValue.Text = "";
                    lblCompHealsInValue.Text = "";
                    lblCompDamageOutValue2.Text = "";
                    lblCompThreatValue2.Text = "";
                    lblCompHealsOutValue2.Text = "";
                    lblCompDamageInValue2.Text = "";
                    lblCompHealsInValue2.Text = "";
                    lblCompHealsOutValue3.Text = "";
                    lblCompHealsOutValue4.Text = "";
                }
            }
        }
        private void BindFightTabs(CombatDetails currentFight)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { BindFightTabs(currentFight); });
                return;
            }
            else
            {
                //DateTime perfDate = DateTime.UtcNow;
                switch (MyTabControl.SelectedIndex)
                {
                    case 1://DamageOut
                        {
                            DateTime perfDate = DateTime.UtcNow;
                            bool showAbilities = rdoDamageOutExpanded.IsChecked ?? false;
                            List<CombatLogEvent> damageOutEvents = currentFight.GetEvents(new CombatLogFilterParameters() { ShowOutgoingDamage = true });
                            Dictionary<long, ActivateDetails> activationDetails = new Dictionary<long,ActivateDetails>();
                            BindDamageOutTargetListList(damageOutEvents.Select(x => x.Target).GroupBy(x => x.GetUniqueKey()).Select(g => g.First()).ToList());
                            string targetToShow = ddlDamageOutTargets.SelectedValue as string;
                            if (targetToShow != "-1")
                            {
                                damageOutEvents = damageOutEvents.Where(x => x.Target.GetUniqueKey() == targetToShow).ToList();
                            }
                            if (showAbilities)
                            {
                                var activates = currentFight.GetActivates();
                                activationDetails = activates.ToDictionary(x => x.Key, x => new ActivateDetails()
                                {
                                    CurrentHitCount = 0,
                                    IsMultiTarget = x.Value.IsMultiTarget,
                                    LastEnemyHitId = x.Value.LastEnemyHitId,
                                    MaxHitCount = x.Value.MaxHitCount,
                                    TimeStamp = x.Value.TimeStamp,
                                    TotalActivationCount = x.Value.TotalActivationCount,
                                    TotalHitCount = x.Value.TotalHitCount,
                                });
                                foreach (CombatLogEvent x in damageOutEvents)
                                {
                                    if (x.Ability != null && x.Ability.IsBasicAttack())
                                    {
                                        ActivateDetails details;
                                        if (activationDetails.TryGetValue(x.Ability.EntityId, out details))
                                        {
                                            details.MaxHitCount = details.GetExpectedHitCount();
                                            x.HitIndex = details.CurrentHitCount;

                                            details.CurrentHitCount++;
                                            if (details.CurrentHitCount >= details.MaxHitCount)
                                                details.CurrentHitCount = 0;
                                        }
                                    }
                                }
                            }

                            List<NestedAbilityDisplayItem> damageOutDetails = damageOutEvents
                                    .GroupBy(x => x.Ability.DisplayName)
                                    .Select(x => new NestedAbilityDisplayItem()
                                    {
                                        Name = x.Key,
                                        Id = x.First().Ability.EntityId,
                                        UseCount = x.Count(),
                                        CritCount = x.Count(y => y.IsCrit),
                                        MissCount = x.Count(y => y.Mitigation.MitigationIsDefense()),
                                        ResistCount = x.Count(y => y.Mitigation.IsResist()),
                                        Total = x.Sum(y => y.Value),
                                        CritTotal = x.Sum(y => y.IsCrit ? y.Value : 0),
                                        DamageTypes = x.Where(z => z.DamageType != null && z.DamageType.EntityId != 0).GroupBy(z => z.DamageType.EntityId).Select(z => new DamageTypeDisplay() { DamageType = z.First().DamageType, DamageSum = z.Sum(v => v.Value) }).ToList(),
                                        Abilities = !showAbilities ? new List<AbilityDisplayItem>() : x.GroupBy(y => y.HitIndex).Select(y => new AbilityDisplayItem()
                                        {
                                            Name = x.Key,
                                            HitIndex = y.Key,
                                            UseCount = y.Count(),
                                            CritCount = y.Count(z => z.IsCrit),
                                            MissCount = y.Count(z => z.Mitigation.MitigationIsDefense()),
                                            ResistCount = y.Count(z => z.Mitigation.IsResist()),
                                            Total = y.Sum(z => z.Value),
                                            CritTotal = y.Sum(z => z.IsCrit ? z.Value : 0),
                                            DamageTypes = y.Where(z => z.DamageType != null && z.DamageType.EntityId != 0).GroupBy(z => z.DamageType.EntityId).Select(z => new DamageTypeDisplay() { DamageType = z.First().DamageType, DamageSum = z.Sum(v => v.Value) }).ToList(),
                                        }).OrderBy(y => y.HitIndex).ToList()
                                    }).OrderByDescending(x => x.Total).ToList();
                            double sumTotal = currentFight.DamageOut;
                            int totalUses = 0;
                            int totalCrits = 0;
                            int totalMisses = 0;
                            if (targetToShow != "-1")
                            {
                                sumTotal = damageOutDetails.Sum(x => x.Total);
                            }
                            foreach (var x in damageOutDetails)
                            {
                                totalUses += x.UseCount;
                                totalCrits += x.CritCount;
                                totalMisses += x.MissCount;
                                x.Pct = sumTotal > 0 ? ((double)x.Total / sumTotal) * 100 : 0;
                                if (x.DamageTypes.Count == 1)
                                {
                                    var dt = (x.DamageTypes.First());
                                    x.DamageColor = dt.DamageType.GetDamageColor();
                                    x.DamageTypeName = dt.DamageType.DisplayName.Capitalize();
                                }
                                else if (x.DamageTypes.Count > 1)
                                {
                                    x.DamageColor = ParsecClientHelpers.GrayTransparentBrush;
                                    x.DamageTypeName = string.Join(", ", x.DamageTypes.Where(y => !string.IsNullOrEmpty(y.DamageType.DisplayName)).Select(y => y.DamageType.DisplayName.Capitalize() + ": " + y.DamageSum.ToString("N0")).Distinct().ToArray());
                                }
                                bool calculateAvgs = true;
                                if (showAbilities)
                                {
                                    ActivateDetails ad;
                                    activationDetails.TryGetValue(x.Id, out ad);
                                    if (ad != null && ad.IsMultiTarget)
                                    {
                                        x.Name = x.Name + " (Multi Target)";
                                        x.Abilities.Clear();
                                    }
                                    else if (ad != null && ad.MaxHitCount <= 1)
                                        x.Abilities.Clear();
                                    else if (ad == null)
                                        x.Abilities.Clear();
                                    else
                                    {
                                        x.Abilities.ForEach(y =>
                                        {
                                            y.Pct = x.Total > 0 ? ((double)y.Total / x.Total) * 100 : 0;
                                            y.CritPct = ((double)y.CritCount / (double)y.UseCount) * 100;
                                            y.Avg = (y.UseCount - (y.MissCount + y.ResistCount)) > 0 ? (double)y.Total / (double)(y.UseCount - (y.MissCount + y.ResistCount)) : 0;
                                            y.CritAvg = y.CritCount > 0 ? (double)y.CritTotal / (double)y.CritCount : 0;
                                            y.AvgNonCrit = ((y.UseCount - (y.MissCount + y.ResistCount)) - y.CritCount) > 0 ? (double)(y.Total - y.CritTotal) / (double)(y.UseCount - (y.MissCount + y.ResistCount) - y.CritCount) : 0;
                                            y.MissPct = ((y.UseCount) > 0 ? (double)(y.MissCount + y.ResistCount) / (double)(y.UseCount) : 0) * 100; ;
                                            if (y.DamageTypes.Count == 1)
                                            {
                                                var dt = (y.DamageTypes.First());
                                                y.DamageColor = dt.DamageType.GetDamageColor();
                                                y.DamageTypeName = dt.DamageType.DisplayName.Capitalize();
                                            }
                                            else if (y.DamageTypes.Count > 1)
                                            {
                                                y.DamageColor = ParsecClientHelpers.GrayTransparentBrush;
                                                y.DamageTypeName = string.Join(", ", y.DamageTypes.Where(z => !string.IsNullOrEmpty(z.DamageType.DisplayName)).Select(z => z.DamageType.DisplayName.Capitalize() + ": " + z.DamageSum.ToString("N0")).Distinct().ToArray());
                                            }
                                            if (ad != null && ad.MaxHitCount > 0)
                                            {
                                                y.Name = y.Name + string.Format(" {0}/{1}", (y.HitIndex + 1).ToString(), ad.MaxHitCount);
                                            }
                                        });
                                        x.UseCount = x.Abilities.GroupBy(y => y.UseCount).OrderByDescending(y => y.Key).FirstOrDefault().Key;
                                        //x.Avg = x.Abilities.Sum(y => y.Avg);
                                        x.CritAvg = null;
                                        x.AvgNonCrit = x.Total / (double)x.UseCount;
                                        x.CritPct = null;
                                        x.MissPct = null;
                                        calculateAvgs = false;
                                    }
                                }
                                if (calculateAvgs)
                                {
                                    x.Avg = (x.UseCount - (x.MissCount + x.ResistCount)) > 0 ? (double)x.Total / (double)(x.UseCount - (x.MissCount + x.ResistCount)) : 0;
                                    x.CritAvg = x.CritCount > 0 ? (double)x.CritTotal / (double)x.CritCount : 0;
                                    x.AvgNonCrit = x.UseCount - (x.MissCount + x.ResistCount + x.CritCount) > 0 ? (double)(x.Total - x.CritTotal) / (double)(x.UseCount - (x.MissCount + x.ResistCount + x.CritCount)) : 0;
                                    x.CritPct = ((double)x.CritCount / (double)x.UseCount) * 100;
                                    x.MissPct = ((x.UseCount) > 0 ? (double)(x.MissCount + x.ResistCount) / (double)(x.UseCount) : 0) * 100;
                                }
                            }
                            lblTotalDmgOut.Text = sumTotal.ToString("N0");
                            lblTotalDmgOutUses.Text = totalUses.ToString("N0");
                            lblTotalDmgOutCrits.Text = ((totalUses > 0 ? (double)totalCrits / (double)totalUses : 0) * 100).ToString("N1");
                            lblTotalDmgOutMisses.Text = ((totalUses > 0 ? (double)totalMisses / (double)totalUses : 0) * 100).ToString("N1");
                            icDamageOutDetails.ItemsSource = damageOutDetails;

                            //Debug.WriteLine("Bind Damage Out Tab: " + DateTime.UtcNow.Subtract(perfDate).TotalMilliseconds);
                        }
                        break;
                    case 2://Heal Out
                        {
                            List<CombatLogEvent> healsOut = new List<CombatLogEvent>(currentFight.GetEvents().Where(x => x.IsSource() && x.IsTargetPlayerOrCompanion() && x.IsHeal()));
                            List<AbilityDisplayItem> healOutDetails = healsOut.GroupBy(x => x.Ability.DisplayName)
                                .Select(x => new AbilityDisplayItem()
                                {
                                    Name = x.Key,
                                    UseCount = x.Count(),
                                    CritCount = x.Count(y => y.IsCrit),
                                    Total = x.Sum(y => y.Value),
                                    CritTotal = x.Sum(y => y.IsCrit ? y.Value : 0),
                                    EffectiveTotal = x.Sum(y => y.EffectiveHeal)
                                }).OrderByDescending(x => x.Total).ToList();

                            long sumTotal = currentFight.HealOut;
                            healOutDetails.ForEach(x =>
                            {
                                x.Pct = sumTotal > 0 ? ((double)x.Total / sumTotal) * 100 : 0;
                                x.CritPct = ((double)x.CritCount / (double)x.UseCount) * 100;
                                x.Avg = (double)x.Total / (double)x.UseCount;
                                x.CritAvg = x.CritCount > 0 ? (double)x.CritTotal / (double)x.CritCount : 0;
                                x.AvgNonCrit = (x.UseCount - x.CritCount) > 0 ? (double)(x.Total - x.CritTotal) / (double)(x.UseCount - x.CritCount) : 0;
                                x.EffectivePct = x.Total > 0 ? ((double)(x.EffectiveTotal) / (double)(x.Total)) * 100 : 0;
                            });
                            icHealOutDetails.ItemsSource = healOutDetails;
                            txtAbsorbCast.Text = currentFight.AbsorbShieldsCast.ToString("N0") + " (" + (currentFight.CombatTimeSpan.TotalMinutes > 0 ? (double)currentFight.AbsorbShieldsCast / currentFight.CombatTimeSpan.TotalMinutes : 0).ToString("N1") + " per min)";
                            if (AbsorbShieldValue > 0)
                            {
                                txtShieldHealing.Visibility = System.Windows.Visibility.Visible;
                                txtShieldHealing.Text = " " + ((currentFight.AbsorbShieldsCast * AbsorbShieldValue) / currentFight.CombatTimeSpan.TotalSeconds).ToString("N2") + " SPS";
                                txtShieldMessage.Visibility = System.Windows.Visibility.Collapsed;
                            }
                            else
                            {
                                txtShieldHealing.Text = "";
                                txtShieldHealing.Visibility = System.Windows.Visibility.Collapsed;
                                if (currentFight.AbsorbShieldsCast > 0)
                                    txtShieldMessage.Visibility = System.Windows.Visibility.Visible;
                            }
                            List<NestedAbilityDisplayItem> healOutTargetDetails = healsOut
                                .GroupBy(x => x.Target.DisplayNameWithOwner)
                                .Select(x => new NestedAbilityDisplayItem()
                                {
                                    Name = x.Key,
                                    UseCount = x.Count(),
                                    Total = x.Sum(y => y.Value),
                                    EffectiveTotal = x.Sum(y => y.EffectiveHeal),
                                    HpsOut = currentFight.CombatTimeSpan.TotalSeconds > 0 ? x.Sum(y => y.Value) / currentFight.CombatTimeSpan.TotalSeconds : 0d,
                                    EhpsOut = currentFight.CombatTimeSpan.TotalSeconds > 0 ? x.Sum(y => y.EffectiveHeal) / currentFight.CombatTimeSpan.TotalSeconds : 0d,
                                }).OrderByDescending(x => x.Total).ToList();
                            healOutTargetDetails.ForEach(x =>
                            {
                                x.Pct = sumTotal > 0 ? ((double)x.Total / sumTotal) * 100 : 0;
                                x.EffectivePct = x.Total > 0 ? ((double)(x.EffectiveTotal) / (double)(x.Total)) * 100 : 0;
                            });
                            icHealOutByTarget.ItemsSource = healOutTargetDetails;
                        }
                        break;
                    case 3://Damage In
                        {
                            List<NestedAbilityDisplayItem> damageInDetails = currentFight.GetEvents(new CombatLogFilterParameters() { ShowIncomingDamage = true })
                                .GroupBy(x => x.Source.DisplayNameWithOwner)
                                .Select(x => new NestedAbilityDisplayItem()
                                {
                                    Name = x.Key,
                                    UseCount = x.Count(),
                                    ShieldCount = x.Count(y => y.Mitigation != null && y.Value > 0),
                                    DefenseCount = x.Count(y => y.Mitigation != null && y.Value == 0),
                                    Total = x.Sum(y => y.Value),
                                    AbsorbedTotal = x.Sum(y => y.AbsorbedValue),
                                    DpsIn = currentFight.CombatTimeSpan.TotalSeconds > 0 ? x.Sum(y => y.Value) / currentFight.CombatTimeSpan.TotalSeconds : 0d,
                                    Abilities = x.GroupBy(y => y.Ability.DisplayName).Select(y => new AbilityDisplayItem()
                                    {
                                        Name = y.Key,
                                        UseCount = y.Count(),
                                        ShieldCount = y.Count(z => z.Mitigation != null && z.Mitigation.EntityId == CombatLogConstants.Mitigation.Shield),
                                        DefenseCount = y.Count(z => z.Mitigation != null && z.Mitigation.EntityId != CombatLogConstants.Mitigation.Shield), //z.Mitigation.MitigationIsDefense()),
                                        AbsorbedTotal = y.Sum(z => z.AbsorbedValue),
                                        Total = y.Sum(z => z.Value),
                                        HitsPerAttack = (int)Math.Round(y.GroupBy(z => Math.Round(((double)z.TimeStamp.Ticks / (double)TimeSpan.TicksPerMillisecond) / 100.0)).Select(z => z.Count()).Average()),
                                        UnmitigatedTotal = y.Where(z => z.Mitigation == null).Sum(z => z.Value),
                                        UnmitigatedCount = y.Where(z => z.Mitigation == null).Count(),
                                        DamageTypes = y.Where(z => z.DamageType != null && z.DamageType.EntityId != 0).GroupBy(z => z.DamageType.EntityId).Select(z => new DamageTypeDisplay() { DamageType = z.First().DamageType, DamageSum = z.Sum(v => v.Value) }).ToList()
                                    }).OrderByDescending(y => y.Total).ToList()
                                }).OrderByDescending(x => x.Total).ToList();
                            long sumTotal = currentFight.DamageIn;
                            long unmitigatedTotal = damageInDetails.Sum(x => x.UnmitigatedTotal);
                            int internalTotal = 0;
                            int elementalTotal = 0;
                            int kineticTotal = 0;
                            int energyTotal = 0;
                            int countTotal = 0;
                            int defenseCountTotal = 0;
                            int shieldCountTotal = 0;
                            long absorbTotal = currentFight.AbsorbedIn;
                            damageInDetails.ForEach(x =>
                            {
                                x.Pct = sumTotal > 0 ? ((double)x.Total / (double)sumTotal) * 100 : 0;
                                x.ShieldPct = ((double)x.ShieldCount / (double)x.UseCount) * 100;
                                x.DefensePct = ((double)x.DefenseCount / (double)x.UseCount) * 100;
                                x.Abilities.ForEach(y =>
                                {
                                    y.Pct = x.Total > 0 ? ((double)y.Total / x.Total) * 100 : 0;
                                    y.ShieldPct = ((double)y.ShieldCount / (double)y.UseCount) * 100;
                                    y.DefensePct = ((double)y.DefenseCount / (double)y.UseCount) * 100;
                                    y.Avg = y.UnmitigatedCount > 0 ? (double)y.UnmitigatedTotal / (double)y.UnmitigatedCount : 0;//(double)y.Total / (double)y.UseCount;
                                    if (y.DamageTypes.Count == 1)
                                    {
                                        var dt = (y.DamageTypes.First());
                                        y.DamageColor = dt.DamageType.GetDamageColor();
                                        y.DamageTypeName = dt.DamageType.DisplayName.Capitalize();
                                    }
                                    else if (y.DamageTypes.Count > 1)
                                    {
                                        y.DamageColor = ParsecClientHelpers.GrayTransparentBrush;
                                        y.DamageTypeName = string.Join(", ", y.DamageTypes.Where(z => !string.IsNullOrEmpty(z.DamageType.DisplayName)).Select(z => z.DamageType.DisplayName.Capitalize() + ": " + z.DamageSum.ToString("N0")).Distinct().ToArray());
                                    }
                                    foreach (var dt in y.DamageTypes)
                                    {
                                        if (dt.DamageType.DisplayName != null && x.DamageByType.ContainsKey(dt.DamageType.DisplayName))
                                            x.DamageByType[dt.DamageType.DisplayName] += dt.DamageSum;
                                        else if (y.DamageTypeName != null)
                                            x.DamageByType.Add(dt.DamageType.DisplayName, dt.DamageSum);
                                        switch (dt.DamageType.EntityId)
                                        {
                                            case CombatLogConstants.DamageTypes.Elemental:
                                                {
                                                    elementalTotal += dt.DamageSum;
                                                    break;
                                                }
                                            case CombatLogConstants.DamageTypes.Internal:
                                                {
                                                    internalTotal += dt.DamageSum;
                                                    break;
                                                }
                                            case CombatLogConstants.DamageTypes.Kinetic:
                                                {
                                                    kineticTotal += dt.DamageSum;
                                                    break;
                                                }
                                            case CombatLogConstants.DamageTypes.Energy:
                                                {
                                                    energyTotal += dt.DamageSum;
                                                    break;
                                                }
                                        }
                                    }
                                    countTotal += y.UseCount;
                                    defenseCountTotal += y.DefenseCount;
                                    shieldCountTotal += y.ShieldCount;
                                });
                            });
                            icDamageInDetails.ItemsSource = damageInDetails;
                            txtElementalDamageIn.Text = (sumTotal > 0 ? ((double)elementalTotal / (double)sumTotal) : 0.0).ToString("P1");
                            txtInternalDamageIn.Text = (sumTotal > 0 ? ((double)internalTotal / (double)sumTotal) : 0.0).ToString("P1");
                            txtEnergyDamageIn.Text = (sumTotal > 0 ? ((double)energyTotal / (double)sumTotal) : 0.0).ToString("P1");
                            txtKineticDamageIn.Text = (sumTotal > 0 ? ((double)kineticTotal / (double)sumTotal) : 0.0).ToString("P1");
                            txtDefenseIn.Text = (countTotal > 0 ? ((double)defenseCountTotal / (double)countTotal) : 0.0).ToString("P1");
                            txtShieldIn.Text = (countTotal > 0 ? ((double)shieldCountTotal / (double)countTotal) : 0.0).ToString("P1");
                            txtAbsorbIn.Text = (countTotal > 0 ? ((double)absorbTotal / (double)sumTotal) : 0.0).ToString("P1");
                            txtAbsorbPerSec.Text = (currentFight.CombatTimeSpan.TotalSeconds > 0 ? (double)absorbTotal / currentFight.CombatTimeSpan.TotalSeconds : 0).ToString("N0");
                            txtElementalDamageIn.ToolTip = elementalTotal.ToString("N0");
                            txtInternalDamageIn.ToolTip = internalTotal.ToString("N0");
                            txtEnergyDamageIn.ToolTip = energyTotal.ToString("N0");
                            txtKineticDamageIn.ToolTip = kineticTotal.ToString("N0");
                            txtAbsorbIn.ToolTip = absorbTotal.ToString("N0");
                        }
                        break;
                    case 4: //Heal In
                        {
                            List<NestedAbilityDisplayItem> healInDetails = currentFight.GetEvents(new CombatLogFilterParameters() { ShowIncomingHeals = true })
                            .GroupBy(x => x.Source.DisplayNameWithOwner)
                            .Select(x => new NestedAbilityDisplayItem()
                            {
                                Name = x.Key,
                                UseCount = x.Count(),
                                Total = x.Sum(y => y.Value),
                                HpsIn = currentFight.CombatTimeSpan.TotalSeconds > 0 ? x.Sum(y => y.Value) / currentFight.CombatTimeSpan.TotalSeconds : 0d,
                                Abilities = x.GroupBy(y => y.Ability.DisplayName).Select(y => new AbilityDisplayItem()
                                {
                                    Name = y.Key,
                                    UseCount = y.Count(),
                                    Total = y.Sum(z => z.Value)
                                }).OrderByDescending(y => y.Total).ToList()
                            }).OrderByDescending(x => x.Total).ToList();
                            double sumTotal = currentFight.HealIn;
                            healInDetails.ForEach(x =>
                            {
                                x.Pct = sumTotal > 0 ? ((double)x.Total / sumTotal) * 100 : 0;
                                x.Abilities.ForEach(y =>
                                {
                                    y.Pct = x.Total > 0 ? ((double)y.Total / x.Total) * 100 : 0;
                                    y.Avg = (double)y.Total / (double)y.UseCount;
                                });
                            });
                            icHealInDetails.ItemsSource = healInDetails;
                        }
                        break;
                    case 5: //threat
                        {
                            bool showThreatList = rdoThreatTargets.IsChecked ?? false;
                            if (showThreatList)
                            {
                                List<AbilityDisplayItem> threatList = new List<AbilityDisplayItem>();
                                threatList = currentFight.GetParticipantList().Where(x => x.IsEnemy && !x.IsPlayer).Select(p =>
                                    new AbilityDisplayItem
                                    {
                                        Name = p.DisplayNameWithOwner,
                                        Threat = p.TotalThreat,
                                        Tps = currentFight.CombatTimeSpan.TotalSeconds > 0 ? p.TotalThreat / currentFight.CombatTimeSpan.TotalSeconds : 0,
                                        Pct = 0.0
                                    }).OrderByDescending(x => x.Threat).ToList();
                                double sumTotal = threatList.Sum(x => x.Threat);
                                threatList.ForEach(x =>
                                {
                                    x.Pct = (x.Threat / sumTotal) * 100;
                                });
                                icThreatDetails.ItemsSource = threatList;
                                grdThreatList.Visibility = System.Windows.Visibility.Visible;
                                grdThreatAbilities.Visibility = System.Windows.Visibility.Collapsed;
                            }
                            else
                            {
                                List<CombatLogEvent> threatOutEvents = currentFight.GetEvents(new CombatLogFilterParameters() { ShowOutgoingDamage = true, ShowOutgoingHeals = true });
                                List<NestedAbilityDisplayItem> threatAbilities = threatOutEvents
                                        .GroupBy(x => x.Ability.DisplayName)
                                        .Select(x => new NestedAbilityDisplayItem()
                                        {
                                            Name = x.Key,
                                            Id = x.First().Ability.EntityId,
                                            UseCount = x.Count(),
                                            CritCount = x.Count(y => y.IsCrit),
                                            MissCount = x.Count(y => y.Mitigation.MitigationIsDefense()),
                                            ResistCount = x.Count(y => y.Mitigation.IsResist()),
                                            Total = x.Sum(y => y.Threat),
                                            CritTotal = x.Sum(y => y.IsCrit ? y.Threat : 0)
                                        }).OrderByDescending(x => x.Total).ToList();
                                double sumTotal = threatAbilities.Sum(x => x.Total);
                                foreach (var x in threatAbilities)
                                {
                                    x.Pct = sumTotal > 0 ? ((double)x.Total / sumTotal) * 100 : 0;
                                    x.Avg = (x.UseCount - (x.MissCount + x.ResistCount)) > 0 ? (double)x.Total / (double)(x.UseCount - (x.MissCount + x.ResistCount)) : 0;
                                    x.CritAvg = x.CritCount > 0 ? (double)x.CritTotal / (double)x.CritCount : 0;
                                    x.AvgNonCrit = x.UseCount - (x.MissCount + x.ResistCount + x.CritCount) > 0 ? (double)(x.Total - x.CritTotal) / (double)(x.UseCount - (x.MissCount + x.ResistCount + x.CritCount)) : 0;
                                    x.CritPct = ((double)x.CritCount / (double)x.UseCount) * 100;
                                    x.MissPct = ((x.UseCount) > 0 ? (double)(x.MissCount + x.ResistCount) / (double)(x.UseCount) : 0) * 100;
                                }
                                icThreatAbilities.ItemsSource = threatAbilities;
                                grdThreatAbilities.Visibility = System.Windows.Visibility.Visible;
                                grdThreatList.Visibility = System.Windows.Visibility.Collapsed;
                            }
                            txtHealThreat.Text = currentFight.HealThreat.ToString("N0") + " (" + (currentFight.CombatTimeSpan.TotalSeconds > 0 ? (double)currentFight.HealThreat / currentFight.CombatTimeSpan.TotalSeconds : 0).ToString("N0") + ")";
                        }
                        break;
                }
            }
        }
        private void ClearFightTabs()
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { ClearFightTabs(); });
                return;
            }
            else
            {
                DateTime perfDate = DateTime.UtcNow;
                icDamageOutDetails.ItemsSource = new List<AbilityDisplayItem>();
                icHealOutDetails.ItemsSource = new List<AbilityDisplayItem>();
                icDamageInDetails.ItemsSource = new List<NestedAbilityDisplayItem>();
                icHealInDetails.ItemsSource = new List<NestedAbilityDisplayItem>();
                icThreatDetails.ItemsSource = new List<AbilityDisplayItem>();
                if (btnStats.IsChecked)
                    ((App)Application.Current).StatPopOut.SetStats(null, AbsorbShieldValue);
                if (btnDpsMeter.IsChecked)
                    ((App)Application.Current).DpsMeterPopOut.BindData(null);
                //Debug.WriteLine("ClearFightTabs: " + DateTime.UtcNow.Subtract(perfDate).TotalMilliseconds);
                txtElementalDamageIn.Text = "0%";
                txtInternalDamageIn.Text = "0%";
                txtEnergyDamageIn.Text = "0%";
                txtKineticDamageIn.Text = "0%";
                txtDefenseIn.Text = "0%";
                txtShieldIn.Text = "0%";
                txtElementalDamageIn.ToolTip = null;
                txtInternalDamageIn.ToolTip = null;
                txtEnergyDamageIn.ToolTip = null;
                txtKineticDamageIn.ToolTip = null;
                txtHealThreat.Text = "0 (0)";
                txtAbsorbCast.Text = "0 (0)";
                BindDamageOutTargetListList(null);
            }
        }
        private void BindCombatLog(List<CombatLogEvent> newEvents, bool clearPrevious)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { BindCombatLog(newEvents, clearPrevious); });
                return;
            }
            else
            {
                DateTime perfDate = DateTime.UtcNow;
                if (clearPrevious && newEvents.Count == 0)
                {
                    MyCombatLogControl.ClearContent();
                }
                else
                {
                    IEnumerable<CombatLogEvent> filteredEvents = newEvents;
                    filteredEvents = filteredEvents.ApplyFilter(LogFilterParameters);
                    IEnumerable<string> filteredStrings = filteredEvents.Select(x => x.ToString(Log.CharacterName, true));
                    filteredStrings = filteredStrings.ApplyFilter(LogFilterParameters);
                    string[] events = filteredStrings.ToArray();
                    if (clearPrevious)
                        MyCombatLogControl.SetLogContent(events);
                    else
                        MyCombatLogControl.AppendLogContent(events);
                }
                //Debug.WriteLine("BindCombatLog: " + DateTime.UtcNow.Subtract(perfDate).TotalMilliseconds);
            }
        }
        private void BindFightList(bool retainSelection, int? newSelectedValue, bool isArchive)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { BindFightList(retainSelection, newSelectedValue, isArchive); });
                return;
            }
            else
            {
                int? previousSelection = (int?)ddlFights.SelectedValue;
                DateTime perfDate = DateTime.UtcNow;
                List<FightDisplayItem> fights = Log.GetFightDisplayItems();
                if (!isArchive)
                {
                    var lastFightDisplayItem = fights.LastOrDefault();
                    if (lastFightDisplayItem != null)
                    {
                        lastFightDisplayItem.Name = "Current Fight";
                    }
                }
                ddlFights.ItemsSource = fights;
                ddlFights.SelectedValuePath = "FightIndex";
                if (retainSelection && previousSelection.HasValue)
                    SetFightDropDownValue(previousSelection.Value);
                else if (newSelectedValue.HasValue)
                    SetFightDropDownValue(newSelectedValue.Value);
                //Debug.WriteLine("BindFightTabs: " + DateTime.UtcNow.Subtract(perfDate).TotalMilliseconds);
            }
        }
        private void BindPopOuts(CombatDetails currentFight, List<CombatLogEvent> newEvents)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { BindPopOuts(currentFight, newEvents); });
                return;
            }
            else
            {
                if (btnStats.IsChecked)
                    ((App)Application.Current).StatPopOut.SetStats(currentFight, AbsorbShieldValue);
                if (btnDpsMeter.IsChecked)
                    ((App)Application.Current).DpsMeterPopOut.BindData(currentFight);

                if (newEvents == null)
                {
                    ((App)Application.Current).StatStopWatchPopOut.StopIfWatching();
                }
                ((App)Application.Current).StatStopWatchPopOut.SetStats(newEvents);
            }
        }
        private void SetFightDropDownValue(int fightIndex)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { SetFightDropDownValue(fightIndex); });
                return;
            }
            else
            {
                if (ddlFights.HasItems)
                {
                    ddlFights.SelectionChanged -= new SelectionChangedEventHandler(ddlFights_SelectionChanged);
                    ddlFights.SelectedValue = fightIndex;
                    ddlFights.SelectionChanged += new SelectionChangedEventHandler(ddlFights_SelectionChanged);

                    int lastPossibleFightIndex = ddlFights.Items.Cast<FightDisplayItem>().Select(x => x.FightIndex).Max();
                    int currentSelectedFightIndex = ddlFights.SelectedValue != null ? Convert.ToInt32(ddlFights.SelectedValue) : 0;
                    IsLastFightSelected = lastPossibleFightIndex == currentSelectedFightIndex;
                }
                else
                    IsLastFightSelected = true;
            }
        }
        public void SetRaidReportTypeDropDownValue(string selectedName)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { SetRaidReportTypeDropDownValue(selectedName); });
                return;
            }
            else
            {
                if (ddlRaidReportType.HasItems)
                {
                    ddlRaidReportType.SelectionChanged -= new SelectionChangedEventHandler(ddlRaidReportType_SelectionChanged);
                    this.ddlRaidReportType.SelectedValue = "cbiDamageDone";
                    if (!string.IsNullOrEmpty(selectedName) && this.ddlRaidReportType.Items.Cast<ComboBoxItem>().FirstOrDefault(x => x.Name == selectedName) != null)
                    {
                        try
                        {
                            this.ddlRaidReportType.SelectedValue = selectedName;
                        }
                        catch
                        {
                            this.ddlRaidReportType.SelectedValue = "cbiDamageDone";
                        }
                    }
                    ddlRaidReportType.SelectionChanged += new SelectionChangedEventHandler(ddlRaidReportType_SelectionChanged);
                }
            }
        }
        public void BindRaidGroups()
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { BindRaidGroups(); });
                return;
            }
            else
            {
                menuRaidGroups.Items.Clear();
                var items = ((App)Application.Current).RaidGroups.Select(x => new { Name = x.Name, IsChecked = string.Compare(x.Name, CurrentRaidGroup.Name, true) == 0 }).ToList();
                foreach (var i in items)
                {
                    MenuItem m = new MenuItem() { IsCheckable = true, IsChecked = i.IsChecked, Header = i.Name, StaysOpenOnClick = true };
                    m.Click += new RoutedEventHandler(btnRaidGroup_Clicked);
                    menuRaidGroups.Items.Add(m);
                }
                if (items.Count > 0)
                    menuRaidGroups.Items.Add(new Separator());
                MenuItem manageButton = new MenuItem() { Header = "Manage", IsCheckable = false, IsChecked = false };
                manageButton.Click += new RoutedEventHandler(btnManageGroups_Click);
                menuRaidGroups.Items.Add(manageButton);
            }
        }
        public void BindRecentFiles()
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { BindRecentFiles(); });
                return;
            }
            else
            {
                btnRecentLogs.Items.Clear();
                if (((App)Application.Current).RecentFiles.Count == 0)
                {
                    btnRecentLogs.IsEnabled = false;
                }
                else
                {
                    btnRecentLogs.IsEnabled = true;
                    var fs = ((App)Application.Current).RecentFiles.Reverse<string>();
                    foreach (string f in fs)
                    {
                        string name = System.IO.Path.GetFileName(f);
                        DateTime d = Parser.ParseDateFromFileName(name, true);
                        if (d != DateTime.Today)
                            name = d.ToString("dddd, MMM. d h:mm tt");
                        MenuItem m = new MenuItem() { Header = name, ToolTip = f };
                        m.Click += new RoutedEventHandler(btnRecentFile_Clicked);
                        btnRecentLogs.Items.Add(m);
                    }
                }
            }
        }
        private void BindThreatMeter(CombatDetails currentFight)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { BindThreatMeter(currentFight); });
                return;
            }
            else
            {
                if (btnRaidTps.IsChecked)
                {
                    ((App)Application.Current).RaidTpsPopOut.BindData(currentFight);
                }
            }
        }
        private void BindRaidDetails(CombatDetails currentFight)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { BindRaidDetails(currentFight); });
                return;
            }
            else
            {
                //get selected fight
                if (currentFight == null)
                {
                    icRaidDetails.ItemsSource = new List<RaidDisplayItem>();
                    ((App)Application.Current).RaidDpsPopOut.BindData(null, null);
                    ((App)Application.Current).RaidHpsPopOut.BindData(null, null);
                    ((App)Application.Current).RaidTpsPopOut.BindData(null);
                    return;
                }
                List<RaidDisplayItem> raidDisplayItems = new List<RaidDisplayItem>();
                IEnumerable<RaidDisplayItem> displayItems = currentFight.GetRaidDetails().Select(x => new RaidDisplayItem()
                {
                    Name = x.CharacterName,
                    DamageOut = x.DamageOut,
                    DamageIn = x.DamageIn,
                    HealIn = x.HealIn,
                    HealOut = x.HealOut,
                    EffectiveHealOut = x.EffectiveHealOut,
                    Threat = x.Threat,
                    CombatTime = x.CombatTimeSpan,
                    DpsIn = x.CombatTimeSpan.TotalSeconds > 0 ? x.DamageIn / x.CombatTimeSpan.TotalSeconds : 0,
                    DpsOut = x.CombatTimeSpan.TotalSeconds > 0 ? x.DamageOut / x.CombatTimeSpan.TotalSeconds : 0,
                    HpsIn = x.CombatTimeSpan.TotalSeconds > 0 ? x.HealIn / x.CombatTimeSpan.TotalSeconds : 0,
                    HpsOut = x.CombatTimeSpan.TotalSeconds > 0 ? x.HealOut / x.CombatTimeSpan.TotalSeconds : 0,
                    EhpsOut = x.CombatTimeSpan.TotalSeconds > 0 ? x.EffectiveHealOut / x.CombatTimeSpan.TotalSeconds : 0,
                    Tps = x.CombatTimeSpan.TotalSeconds > 0 ? x.Threat / x.CombatTimeSpan.TotalSeconds : 0,
                    EffectivePct = x.HealOut > 0 ? ((double)x.EffectiveHealOut / (double)x.HealOut) * 100 : 0,
                    EnemyParticipants = x.GetParticipants(),
                    IsCurrentCharacter = x.CharacterName == currentFight.CharacterName,
                    ShieldingOut = x.AbsorbShieldsCast * x.AbsorbShieldValue,
                    SpsOut = x.CombatTimeSpan.TotalSeconds > 0 ? ((x.AbsorbShieldsCast * x.AbsorbShieldValue) / x.CombatTimeSpan.TotalSeconds) : 0
                });

                if (cbiDamageDone.IsSelected)
                    displayItems = displayItems.OrderByDescending(x => x.DamageOut);
                else if (cbiDamageIn.IsSelected)
                    displayItems = displayItems.OrderByDescending(x => x.DamageIn);
                else if (cbiHealsDone.IsSelected)
                    displayItems = displayItems.OrderByDescending(x => x.HealOut);
                else if (cbiEffectiveOut.IsSelected)
                    displayItems = displayItems.OrderByDescending(x => x.EffectiveHealOut);
                else if (cbiThreat.IsSelected)
                    displayItems = displayItems.OrderByDescending(x => x.Threat);
                else if (cbiDPS.IsSelected)
                    displayItems = displayItems.OrderByDescending(x => x.DpsOut);
                else if (cbiDTPS.IsSelected)
                    displayItems = displayItems.OrderByDescending(x => x.DpsIn);
                else if (cbiHPS.IsSelected)
                    displayItems = displayItems.OrderByDescending(x => x.HpsOut);
                else if (cbiEHPS.IsSelected)
                    displayItems = displayItems.OrderByDescending(x => x.EhpsOut);
                else if (cbiTPS.IsSelected)
                    displayItems = displayItems.OrderByDescending(x => x.Tps);

                raidDisplayItems = displayItems.ToList();
                icRaidDetails.ItemsSource = raidDisplayItems;
                if (btnRaidDps.IsChecked)
                {
                    ((App)Application.Current).RaidDpsPopOut.BindData(raidDisplayItems, currentFight.CharacterName);
                }
                if (btnRaidHps.IsChecked)
                {
                    ((App)Application.Current).RaidHpsPopOut.BindData(raidDisplayItems, currentFight.CharacterName);
                }
                if (btnRaidTps.IsChecked)
                {
                    ((App)Application.Current).RaidTpsPopOut.BindData(currentFight);
                }
            }
        }
        public void BindConnectedRaiders(bool clearRaiders)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { BindConnectedRaiders(clearRaiders); });
                return;
            }
            else
            {
                if (!clearRaiders)
                {
                    txtRaidGroupRaiders.Visibility = System.Windows.Visibility.Visible;
                    txtRaidGroupRaiders.Text = string.Join(", ", GetConnectedRaiders().ToArray());
                }
                else
                {
                    txtRaidGroupRaiders.Visibility = System.Windows.Visibility.Collapsed;
                    txtRaidGroupRaiders.Text = null;
                }
            }
        }
        public void ShowNotes()
        {
            ReleaseNotes s = new ReleaseNotes();
            s.Owner = this;
            s.ShowDialog();
        }
        public void ApplyCharacterSettings(CharacterSettings settings)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { ApplyCharacterSettings(settings); });
                return;
            }
            else
            {
                if (settings != null)
                {
                    SetRaidReportTypeDropDownValue(settings.RaidDetailsSortKey);
                    AbsorbShieldValue = settings.AbsorbShieldValue;
                    rdoDamageOutCollapsed.IsChecked = settings.UseBasicDpsView;
                    rdoDamageOutExpanded.IsChecked = !settings.UseBasicDpsView;
                    if (!CurrentLogFileIsArchive)
                    {
                        if (settings.ShowStatPopOut != btnStats.IsChecked)
                        {
                            btnStats.IsChecked = settings.ShowStatPopOut;
                            btnStats_Click(btnStats, new RoutedEventArgs());
                        }
                        if (settings.ShowPvpStatsPopOut != btnStatStopWatch.IsChecked)
                        {
                            btnStatStopWatch.IsChecked = settings.ShowPvpStatsPopOut;
                            btnStatStopWatch_Click(btnStatStopWatch, new RoutedEventArgs());
                        }
                        if (settings.ShowRaidDpsPopOut != btnRaidDps.IsChecked)
                        {
                            btnRaidDps.IsChecked = settings.ShowRaidDpsPopOut;
                            btnRaidDps_Click(btnRaidDps, new RoutedEventArgs());
                        }
                        if (settings.ShowRaidTpsPopOut != btnRaidTps.IsChecked)
                        {
                            btnRaidTps.IsChecked = settings.ShowRaidTpsPopOut;
                            btnRaidTps_Click(btnRaidTps, new RoutedEventArgs());
                        }
                        if (settings.ShowRaidHpsPopOut != btnRaidHps.IsChecked)
                        {
                            btnRaidHps.IsChecked = settings.ShowRaidHpsPopOut;
                            btnRaidHps_Click(btnRaidHps, new RoutedEventArgs());
                        }
                        if (((App)Application.Current).AreTimersEnabled && settings.ShowTimerPopOut != btnTimerPop.IsChecked)
                        {
                            btnTimerPop.IsChecked = settings.ShowTimerPopOut;
                            btnTimerPop_Click(btnTimerPop, new RoutedEventArgs());
                        }
                        if (settings.ShowDpsMeterPopOut != btnDpsMeter.IsChecked)
                        {
                            btnDpsMeter.IsChecked = settings.ShowDpsMeterPopOut;
                            btnDpsMeter_Click(btnDpsMeter, new RoutedEventArgs());
                        }
                    }
                }
            }
        }
        public void CloseAllPopOuts()
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { CloseAllPopOuts(); });
                return;
            }
            else
            {
                if (btnStats.IsChecked)
                {
                    btnStats.IsChecked = false;
                    btnStats_Click(btnStats, new RoutedEventArgs());
                }
                if (btnStatStopWatch.IsChecked)
                {
                    btnStatStopWatch.IsChecked = false;
                    btnStatStopWatch_Click(btnStatStopWatch, new RoutedEventArgs());
                }
                if (btnRaidDps.IsChecked)
                {
                    btnRaidDps.IsChecked = false;
                    btnRaidDps_Click(btnRaidDps, new RoutedEventArgs());
                }
                if (btnRaidTps.IsChecked)
                {
                    btnRaidTps.IsChecked = false;
                    btnRaidTps_Click(btnRaidTps, new RoutedEventArgs());
                }
                if (btnRaidHps.IsChecked)
                {
                    btnRaidHps.IsChecked = false;
                    btnRaidHps_Click(btnRaidHps, new RoutedEventArgs());
                }
                if (btnTimerPop.IsChecked)
                {
                    btnTimerPop.IsChecked = false;
                    btnTimerPop_Click(btnTimerPop, new RoutedEventArgs());
                }
                if (btnDpsMeter.IsChecked)
                {
                    btnDpsMeter.IsChecked = false;
                    btnDpsMeter_Click(btnDpsMeter, new RoutedEventArgs());
                }
            }
        }
        public void DeselectAllTimers()
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { DeselectAllTimers(); });
                return;
            }
            else
            {
                ((App)Application.Current).GetTimerDefinitions().ForEach(x => { x.IsEnabled = false; });
                BindTimerDefinitions();
            }
        }
        public void UpdateActiveTimers()
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { UpdateActiveTimers(); });
                return;
            }
            else
            {
                if (btnTimerPop.IsChecked)
                {
                    ((App)Application.Current).TimerPopOut.BindData(GetActiveTimers());
                }
            }
        }
        public void RecursiveMenuBind(MenuItem parent, NestedCategory category)
        {
            foreach (var cat in category.Categories)
            {
                //create a new parent and then resursive bind
                MenuItem newParentMenu = new MenuItem() { Header = cat.Category };
                RecursiveMenuBind(newParentMenu, cat);
                parent.Items.Add(newParentMenu);
            }
            foreach(var i in category.Timers)
            {
                MenuItem m = new MenuItem() { IsCheckable = true, IsChecked = i.IsEnabled, Header = i.Description, StaysOpenOnClick = true };
                m.Click += new RoutedEventHandler(btnTimerEnable_Clicked);
                parent.Items.Add(m);
            }
        }
        public void BindTimerDefinitions()
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { BindTimerDefinitions(); });
                return;
            }
            else
            {
                menuTimers.Items.Clear();
                var tds = ((App)Application.Current).GetTimerDefinitions();
                NestedCategory categories = new NestedCategory();
                foreach (var td in tds.OrderBy(x=>x.Category).ThenBy(x=>x.Description).ToList())
                {
                    string catString = td.Category;
                    if (string.IsNullOrEmpty(catString))
                    {
                        //add item to base
                        categories.Timers.Add(td);
                    }
                    else
                    {
                        List<string> cats = catString.Split('\\').ToList();
                        NestedCategory parent = categories;
                        foreach (string c in cats)
                        {
                            var found = parent.Categories.FirstOrDefault(x => string.CompareOrdinal(x.Category, c) == 0);
                            if (found == null)
                            {
                                //not found so make it
                                NestedCategory newCat = new NestedCategory() { Category = c };
                                parent.Categories.Add(newCat);
                                parent = newCat;
                            }
                            else
                            {
                                parent = found;
                            }
                        }
                        //add item to parent
                        parent.Timers.Add(td);
                    }
                }
                RecursiveMenuBind(menuTimers, categories);
                //var items = tds.Select(x => new { Name = x.Description, IsChecked = x.IsEnabled }).OrderBy(x => x.Name).ToList();
                //foreach (var i in items)
                //{
                //    MenuItem m = new MenuItem() { IsCheckable = true, IsChecked = i.IsChecked, Header = i.Name, StaysOpenOnClick = true };
                //    m.Click += new RoutedEventHandler(btnTimerEnable_Clicked);
                //    menuTimers.Items.Add(m);
                //}
                if (tds.Count > 0)
                    menuTimers.Items.Add(new Separator());
                MenuItem manageTimersButton = new MenuItem() { Header = "Manage", IsCheckable = false, IsChecked = false };
                MenuItem clearTimersButton = new MenuItem() { Header = "Clear Active Timers", IsCheckable = false, IsChecked = false };
                manageTimersButton.Click += new RoutedEventHandler(btnManageTimers_Click);
                clearTimersButton.Click += new RoutedEventHandler(btnClearTimers_Click);
                menuTimers.Items.Add(manageTimersButton);
                menuTimers.Items.Add(clearTimersButton);
            }
        }
        private void BindDamageOutTargetListList(List<CombatLogParticipant> targets)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate { BindDamageOutTargetListList(targets); });
                return;
            }
            else
            {
                IgnoreDamageOutSelectionChanged = true;
                ddlDamageOutTargets.SelectionChanged -= ddlDamageOutTargets_SelectionChanged;
                if (targets == null)
                {
                    var displayList = new List<object>() { new { Text = "All Targets", Value = "-1" } };
                    ddlDamageOutTargets.ItemsSource = displayList;
                    ddlDamageOutTargets.DisplayMemberPath = "Text";
                    ddlDamageOutTargets.SelectedValuePath = "Value";
                    ddlDamageOutTargets.SelectedValue = "-1";
                }
                else
                {
                    string previousSelection = ddlDamageOutTargets.SelectedValue as string;
                    var displayList = targets.Select(x => new { Text = x.ToString(), Value = x.GetUniqueKey() }).ToList();
                    displayList.Insert(0, new { Text = "All Targets", Value = "-1" });
                    ddlDamageOutTargets.ItemsSource = displayList;
                    ddlDamageOutTargets.DisplayMemberPath = "Text";
                    ddlDamageOutTargets.SelectedValuePath = "Value";
                    if (previousSelection == null || (string)previousSelection == string.Empty || displayList.FirstOrDefault(x => x.Value == previousSelection) == null)
                    {
                        previousSelection = "-1";
                    }
                    ddlDamageOutTargets.SelectedValue = previousSelection;
                }
                ddlDamageOutTargets.SelectionChanged += ddlDamageOutTargets_SelectionChanged;
                IgnoreDamageOutSelectionChanged = false;
            }
        }
        #endregion

        #region Other Helper Methods
        private void ProcessLogFile()
        {
            //DateTime perfDate = DateTime.UtcNow;
            if (LogReader != null && (LogReader.FolderPath != LogFolderPath || LogReader.FileName != LogFileFullPath || CurrentLogFileIsArchive))
            {
                LogReader.Dispose();
                LogReader = null;
            }
            if (LogReader == null)
            {
                LogReader = new RealTimeLogReader(LogFileFullPath, LogFolderPath, LogUpdatedHandler, NewLogCreatedHandler, LogReaderError, RaidUpdateTick);
                LogReader.TickWhenLogIsNotUpdated = true;
                LogReader.ReadPollMilliseconds = FilePollingRate;
                LogReader.NetworkPollMilliseconds = NetworkPollingRate;
            }
            //Debug.WriteLine("InitLogReader: " + DateTime.UtcNow.Subtract(perfDate).TotalMilliseconds);
            //perfDate = DateTime.UtcNow;
            LogParser = new Parser();
            Log = new CombatLog(LogParser, System.IO.Path.GetFileName(LogReader.FileName));
            //Debug.WriteLine("InitCombatLog: " + DateTime.UtcNow.Subtract(perfDate).TotalMilliseconds);
            //perfDate = DateTime.UtcNow;
            if (CurrentLogFileIsArchive)
            {
                SetArchiveMessage(System.Windows.Visibility.Visible, Parser.ParseDateFromFileName(Log.FileName, true).ToString("dddd, MMM. d h:mm tt"), Log.FileName, new Uri(LogFileFullPath));
            }
            else
            {
                SetArchiveMessage(System.Windows.Visibility.Collapsed, null, null, null);
                //Log.FightStarted += FightStartedEventHandler;
            }
            List<string> existingLines = LogReader.ReadNewContent();
            //Debug.WriteLine("ReadLog: " + DateTime.UtcNow.Subtract(perfDate).TotalMilliseconds);
            //perfDate = DateTime.UtcNow;
            bool combatStartEvent = false;
            Log.AddLines(existingLines, out combatStartEvent);
            ArchivedRaidFile rdf = ArchivedRaidFight.RetrieveRaidData(Log.FileName);
            List<CombatDetails> fights = Log.GetFightList();
            if (rdf != null && rdf.RaidFights != null && rdf.RaidFights.Count > 0 && fights.Count > 0)
            {
                foreach (var f in rdf.RaidFights)
                {
                    CombatDetails c = null;
                    if (f.FightStart != DateTime.MinValue)
                    {
                        //new method
                        c = fights.FirstOrDefault(x => x != null && x.StartCombat == f.FightStart);
                        if (c != null)
                        {
                            c.SetRaidDetails(f.FightMembers.Select(x => x.ConvertToCombatStatistics()));
                        }
                    }
                    else
                    {
                        //old method
                        var yourRecord = f.FightMembers.FirstOrDefault(x => Log.CharacterName == x.CharacterName);
                        if (yourRecord != null)
                        {
                            DateTime yourStartTime = yourRecord.StartCombat;
                            c = fights.FirstOrDefault(x => x != null && yourStartTime.Subtract(x.StartCombat.ToUniversalTime()).TotalMilliseconds < 200);
                            if (c != null)
                            {
                                c.SetRaidDetails(f.FightMembers.Select(x => x.ConvertToCombatStatistics()));
                            }
                        }
                    }
                }
            }
            //Debug.WriteLine("ParseLog: " + DateTime.UtcNow.Subtract(perfDate).TotalMilliseconds);
            //Debug.WriteLine("LinesParsed: " + existingLines.Count.ToString());
            BindFightList(false, null, true);
            UpdateCharacterName(true);
            BindPopOuts(null, null);
            BindRaidDetails(null);
            BindConnectedRaiders(true);

            //perfDate = DateTime.UtcNow;
            //Debug.WriteLine("PreBoundData: " + DateTime.UtcNow.Subtract(perfDate).TotalMilliseconds);
            if (Log.LastFightIndex > 0) //Fight found
            {
                CombatDetails currentFight = Log.GetFight(Log.LastFightIndex);
                //Debug.WriteLine("FightEventCount: " + currentFight.Events.Count.ToString());
                List<CombatLogEvent> existingEvents = currentFight.GetEvents();
                if (existingEvents.Count > 0)
                {
                    SetFightDropDownValue(Log.LastFightIndex);
                    UpdateStats(currentFight, Log.LogStatistics);
                    BindFightTabs(currentFight);
                    BindCombatLog(existingEvents, true);
                    BindRaidDetails(currentFight);
                    BindPopOuts(currentFight, null);
                }
            }
            else
            {
                //clear interface
                UpdateStats(null, null);
                ClearFightTabs();
                BindCombatLog(new List<CombatLogEvent>(), true);
            }
            //perfDate = DateTime.UtcNow;
            //Debug.WriteLine("BoundData: " + DateTime.UtcNow.Subtract(perfDate).TotalMilliseconds);
        }
        public void OpenLogFile(string openFilePath)
        {
            try
            {
                if (!string.IsNullOrEmpty(openFilePath))
                {
                    LogFileFullPath = openFilePath;
                    CurrentLogFileIsArchive = true;
                    ProcessLogFile();
                    ((App)Application.Current).AddRecentFile(openFilePath);
                    BindRecentFiles();
                }
            }
            catch (Exception ex)
            {
                ((App)Application.Current).HandleError("Error Loading File", "Error on Window_Loaded FileOpen", ex, this, true);
            }
        }
        private void StartWatching()
        {
            IsWatching = true;
            CurrentLogFileIsArchive = false;
            ProcessLogFile();
            LogReader.StartWatchingFile();
            LogReader.StartWatchingFolder();
        }
        private void StopWatching()
        {
            bool wasWatching = IsWatching;
            IsWatching = false;
            if (LogReader != null)
            {
                LogReader.StopWatchingFile();
                LogReader.StopWatchingFolder();
                LogReader.StopReading();
            }
            //archive data
            if (wasWatching && ((App)Application.Current).IsArchiveEnabled && Log != null && Log.LastFightIndex > 0)
            {
                int archiveIndex = Log.LastFightIndex;
                CombatDetails archiveFight = Log.GetFight(archiveIndex);
                if (archiveFight != null)
                {
                    var raidDetails = archiveFight.GetRaidDetails();
                    if (raidDetails.Count > 0)
                    {
                        //archive raid
                        List<ArchivedRaidFightMember> fmem = raidDetails.Select(x => ArchivedRaidFightMember.ConvertToArchivedRaidFightItem(x)).ToList();
                        ArchivedRaidFight.ArchiveRaidData(Log.FileName, new ArchivedRaidFight() { FightIndex = archiveIndex, FightStart = archiveFight.StartCombat, FightMembers = fmem });
                    }
                }
            }
            if (wasWatching && Log != null && !string.IsNullOrEmpty(Log.CharacterName))
            {
                ((App)Application.Current).SaveCharacterSettings(Log.CharacterName);
            }
        }
        private void SendRaidServerHeartBeat(CombatDetails c, bool sendData, ref int requestedPollRate)
        {
            if (c == null)
            {
                c = new CombatDetails();
            }
            if (string.IsNullOrEmpty(Log.CharacterName))
                throw new Exception("You must be logged in to a character to connect to a raid group");
            var p = new ParsecRaidParameters();
            p.RaidGroup = CurrentRaidGroup.Name;
            p.RaidPassword = CurrentRaidGroup.Password;
            string threatListString = c.GetParticipantList().Where(x => x.IsEnemy && !x.IsPlayer).ConvertThreatListToString();
            p.Statistics = new ParsecRaidUser()
            {
                CharacterName = Log.CharacterName,
                RaidUserId = Log.RaidUserId,
                DamageOut = sendData ? c.DamageOut : 0,
                DamageIn = sendData ? c.DamageIn : 0,
                HealOut = sendData ? c.HealOut : 0,
                HealIn = sendData ? c.HealIn : 0,
                Threat = sendData ? c.Threat : 0,
                EffectiveHealOut = sendData ? c.EffectiveHealOut : 0,
                CombatTicks = sendData ? c.CombatTimeSpan.Ticks : 0,
                CombatStart = c.StartCombat.ToUniversalTime(),
                CombatEnd = c.EndCombat.HasValue ? c.EndCombat.Value.ToUniversalTime() : c.EndCombat,
                RaidEncounterId = sendData ? c.RaidEncounterDetail.RaidEncounterId : 0,
                RaidEncounterMode = sendData ? (int)c.RaidEncounterDetail.Mode : 0,
                RaidEncounterPlayers = sendData ? (int)c.RaidEncounterDetail.Players : 0,
                ThreatList = threatListString,
                ParsecVersion = App.AppVersion,
                AbsorbShieldsCast = sendData ? c.AbsorbShieldsCast : 0,
                AbsorbShieldValue = sendData ? AbsorbShieldValue : 0.0
            };
            DateTime beforeDate = DateTime.UtcNow;
            ParsecRaidResponse r = null;
            TimeSpan syncTime;
            using (ParsecRaidServiceSoapClient service = new ParsecRaidServiceSoapClient())
            {
                if (((App)Application.Current).UseRaidServiceCompression)
                {
                    string compressedResponse = sendData ? service.SyncRaidStatsCompressed(ParsecRaidServiceHelpers.StringifyParameters(p, true)) : service.GetRaidStatsCompressed(ParsecRaidServiceHelpers.StringifyParameters(p, true));
                    syncTime = DateTime.UtcNow.Subtract(beforeDate);
                    if (!string.IsNullOrEmpty(compressedResponse))
                        r = ParsecRaidServiceHelpers.DeStringifyResponse(compressedResponse, false);
                }
                else
                {
                    r = sendData ? service.SyncRaidStats(p) : service.GetRaidStats(p);
                    syncTime = DateTime.UtcNow.Subtract(beforeDate);
                }
            }
            if (LastTenSyncs.Count == 10)
            {
                int li = 0;
                LastTenSyncs.TryDequeue(out li);
            }
            LastTenSyncs.Enqueue(Convert.ToInt32(syncTime.TotalMilliseconds));
            if (sendData && r != null && r.Users != null && r.Users.Count > 1)
                LastCombatRaidSync = DateTime.UtcNow;
            LastRaidSyncDate = DateTime.UtcNow;
            if (string.IsNullOrEmpty(r.ErrorMessage))
            {
                requestedPollRate = r.MinimumPollingRate;
                DateTime? lastRaidCombatUpdate = r.Users.DefaultIfEmpty(new ParsecRaidUser()).Max(x => x.LastCombatUpdate);
                if (lastRaidCombatUpdate.HasValue && lastRaidCombatUpdate == RaidLastUpdatedDate)
                {
                    ConsecutiveRaidHeartBeatWithNoUpdate++;
                }
                else
                {
                    ConsecutiveRaidHeartBeatWithNoUpdate = 0;
                    RaidLastUpdatedDate = lastRaidCombatUpdate;
                }
                //only update raid details if you are in combat or you were in combat and raid connected
                if (sendData || c.DataHasBeenSynced)
                {
                    c.DataHasBeenSynced = true;
                    c.RaidGroupName = CurrentRaidGroup.Name;
                    //if users dropped out keep them in if the fight hasnt changed
                    if (ConsecutiveRaidHeartBeatWithNoUpdate == 0 && RaidUpdateMode != RaidUpdateModes.SleepSync) //only update data if a change was made
                    {
                        //find users that may have dropped and keep them
                        List<CombatStatistics> userList =  c.GetRaidDetails().Where(x => r.Users.FirstOrDefault(y => y.CharacterName == x.CharacterName) == null).ToList();
                        userList.AddRange(r.Users.Where(x => x.LastCombatUpdate.HasValue).Select(x => new CombatStatistics(x.ThreatList.ConvertToParticipantList() ?? new Dictionary<string, CombatLogParticipant>())
                         {
                             CharacterName = x.CharacterName,
                             DamageIn = x.DamageIn,
                             DamageOut = x.DamageOut,
                             HealIn = x.HealIn,
                             HealOut = x.HealOut,
                             EffectiveHealOut = x.EffectiveHealOut,
                             Threat = x.Threat,
                             CombatTicks = x.CombatTicks,
                             CombatTimeSpan = new TimeSpan(x.CombatTicks),
                             StartCombat = x.CombatStart.HasValue ? x.CombatStart.Value : DateTime.MinValue,
                             EndCombat = x.CombatEnd,
                             RaidEncounterDetail = new RaidEncounterDetails() { RaidEncounterId = x.RaidEncounterId },
                             AbsorbShieldsCast = x.AbsorbShieldsCast,
                             AbsorbShieldValue = x.AbsorbShieldValue
                         }));
                        c.SetRaidDetails(userList);
                    }
                }
                var u = r.Users.FirstOrDefault(x => x.CharacterName == Log.CharacterName);
                if (u != null)
                    Log.RaidUserId = u.RaidUserId;
                SetConnectedRaiders(r.Users.Where(x => x.IsConnected).Select(x => x.CharacterName).OrderBy(x => x).ToList());
            }
            else
                throw new Exception(r.ErrorMessage);
        }
        private void DisconnectFromRaid(string characterName, bool clearRaiders)
        {
            LogReader.StopNetworkTimer();
            IsRaidConnected = false;
            if (!string.IsNullOrEmpty(characterName))
            {
                var p = new ParsecRaidParameters();
                p.RaidGroup = CurrentRaidGroup.Name;
                p.RaidPassword = CurrentRaidGroup.Password;
                p.Statistics = new ParsecRaidUser()
                {
                    CharacterName = characterName
                };
                using (ParsecRaidServiceSoapClient service = new ParsecRaidServiceSoapClient())
                {
                    var r = service.Disconnect(p);
                    Log.RaidUserId = 0;
                    ClearConnectedRaiders();
                    if (!string.IsNullOrEmpty(r.ErrorMessage))
                        throw new Exception(r.ErrorMessage);
                }
            }
            if (clearRaiders)
                BindConnectedRaiders(true);
        }
        public void ProcessTimers()
        {
            List<ActiveTimer> removals = new List<ActiveTimer>();
            var ats = GetActiveTimers();
            //record timers that ended
            foreach (ActiveTimer a in ats)
            {
                if (a.EndTime.CompareTo(((App)Application.Current).GetCurrentServerTime().AddMilliseconds(100)) <= 0)
                {
                    //Debug.WriteLine(string.Format("Timer: {0} Detected: {1} Start: {2} End: {3}  Triggered: {4} ", a.Description, a.DetectedTime.ToString("h:mm:ss fff"), a.StartTime.ToString("h:mm:ss fff"), a.EndTime.ToString("h:mm:ss fff"), DateTime.Now.ToString("h:mm:ss fff")));
                    if (a.SoundEffect != TimerSound.None)
                    {

                        if (a.SoundEffect == TimerSound.TextToSpeech)
                        {
                            ParsecClientHelpers.PlayTextToSpeech(a.TextToSpeechText);
                        }
                        else if (a.SoundEffect == TimerSound.Custom)
                        {
                            ParsecClientHelpers.PlayCustomSound(a.CustomSoundPath);
                        }
                        else
                        {
                            ParsecClientHelpers.PlaySound(a.SoundEffect);
                        }
                    }
                    if (a.RepeatCount < 99)
                        a.RepeatCount--;
                    if (a.RepeatCount < 0)
                        removals.Add(a);
                    else
                    {
                        double dur = a.EndTime.Subtract(a.StartTime).TotalSeconds;
                        a.StartTime = a.EndTime;
                        a.EndTime = a.StartTime.AddSeconds(dur);
                    }
                }
            }
            if (removals.Count > 0)
            {
                //List<ActiveTimer> chainedRemovals = new List<ActiveTimer>();
                foreach (ActiveTimer a in ats)
                {
                    foreach (ActiveTimer rat in removals)
                    {
                        if (string.Compare(a.DisplayName, rat.DisplayName, true) == 0)
                        {
                            //removal already occuring so skip it
                        }
                        else
                        {
                            if (string.Compare(a.CancelTimerName, rat.DisplayName, true) == 0)
                            {
                                //chainedRemovals.Add(a);
                                RemoveActiveTimer(a);
                            }
                        }
                    }
                }
                //removals.AddRange(chainedRemovals);
                List<ActiveTimer> chainedTimers = new List<ActiveTimer>();
                var timerDefs = ((App)Application.Current).GetTimerDefinitions();
                foreach (ActiveTimer rat in removals)
                {
                    //check if any active timers have a start event for this removal
                    var newChainedTimers = timerDefs.Where(x => x.TriggerType == TimerTriggerType.TimerExpires && string.Compare(x.CustomAbilityOrEffect, rat.DisplayName, true) == 0).Select(x => x.GetActiveTimer(((App)Application.Current).GetCurrentServerTime(), ((App)Application.Current).GetCurrentServerTime(), new CombatLogParticipant())).ToList();
                    //chainedTimers.AddRange(newChainedTimers);
                    foreach (var nct in newChainedTimers)
                    {
                        var chainedTimer = chainedTimers.FirstOrDefault(x => x.DisplayName == nct.DisplayName);
                        if (chainedTimer != null)
                        {
                            if (nct.MultiTimerMode == MultipleTimerMode.UpdateTimerOnRepeat)
                            {
                                chainedTimer.StartTime = nct.StartTime;
                                chainedTimer.EndTime = nct.EndTime;
                                chainedTimer.DetectedTime = nct.DetectedTime;
                                chainedTimer.RepeatCount = nct.RepeatCount;
                            }
                        }
                        else
                        {
                            chainedTimers.Add(nct);
                        }
                    }
                    RemoveActiveTimer(rat);
                }

                ats = GetActiveTimers(); //refresh actives
                //check if anychains are ignored/updated and add the ones that need it
                List<string> updateRemovals = new List<string>();
                List<string> ignoreTimers = new List<string>();
                foreach (var ct in chainedTimers)
                {
                    if (ct.MultiTimerMode != MultipleTimerMode.AllowMultipleTimers)
                    {
                        var currentActiveTimer = ats.FirstOrDefault(x => x.DisplayName == ct.DisplayName);
                        if (currentActiveTimer != null)
                        {
                            if (ct.MultiTimerMode == MultipleTimerMode.UpdateTimerOnRepeat)
                            {
                                updateRemovals.Add(currentActiveTimer.DisplayName);
                            }
                            else
                            {
                                ignoreTimers.Add(ct.DisplayName);
                            }
                        }
                    }
                }
                foreach (var it in ignoreTimers)
                {
                    //Debug.WriteLine("Ignoring: " + it);
                    chainedTimers.RemoveAll(x => x.DisplayName == it);
                }
                foreach (string ur in updateRemovals)
                {
                    //Debug.WriteLine("Removing for Update: " + ur);
                    RemoveActiveTimer(ur);
                }
                foreach (var ct in chainedTimers)
                {
                    //Debug.WriteLine("Adding: " + ct.DisplayName);
                    AddActiveTimer(ct);
                }
            }
        }
        #endregion
        
        #region Collection Helpers
        public List<string> GetConnectedRaiders()
        {
            lock (ConnectedRaiderLock)
            {
                return ConnectedRaiders.ToList();
            }
        }
        private void SetConnectedRaiders(IEnumerable<string> raiderNames)
        {
            lock (ConnectedRaiderLock)
            {
                ConnectedRaiders = new List<string>(raiderNames);
            }
        }
        private void ClearConnectedRaiders()
        {
            lock (ConnectedRaiderLock)
            {
                ConnectedRaiders.Clear();
            }
        }
        public void AddActiveTimer(ActiveTimer a)
        {
            lock (ActiveTimerLock)
            {
                ActiveTimers.Add(a);
            }
        }
        public void ClearActiveTimers()
        {
            lock (ActiveTimerLock)
            {
                ActiveTimers.Clear();
            }
        }
        public void RemoveActiveTimer(string timerName)
        {
            lock (ActiveTimerLock)
            {
                ActiveTimers.RemoveAll(x => string.Compare(x.TriggerId, timerName, false) == 0);
            }
        }
        public void RemoveActiveTimer(ActiveTimer t)
        {
            lock (ActiveTimerLock)
            {
                ActiveTimers.Remove(t);
            }
        }
        public ActiveTimer GetActiveTimer(string timerName)
        {
            lock (ActiveTimerLock)
            {
                return ActiveTimers.FirstOrDefault(x => string.Compare(x.TriggerId, timerName, false) == 0);
            }
        }
        public List<ActiveTimer> GetActiveTimers()
        {
            lock (ActiveTimerLock)
            {
                return ActiveTimers.ToList();
            }
        }
        #endregion
    }
}
